Sunday, February 23, 2020

Programming an Ethereum Smart Contract with Vyper

and deploying it with MetaMask and MyEtherWallet (MEW)


Introduction

Ethereum has a vast amount of developer resources available, such that for a beginner its hard to know where to start.

If you go with the recommended approach you'd probably choose Solidity along with either JavaScript or golang. JavaScript and golang are traditionally the most well supported languages for the developer tooling around Ethereum. The original Ethereum node software, geth, is written in golang. There's a version of the Solidity smart contract compiler, solcjs, written in JavaScript, and available as a Node.js NPM package.

The other Smart Contract language that is supported by the Ethereum Foundation is Vyper. Vyper is a python-like language that was designed for security, simplicity and testability. In my view this makes Vyper ideal for a beginner. Vyper foregoes some of the power of Solidity (for example, class inheritance and function modifiers) in order to uphold the Vyper principles of being simple and secure.

In this article I'll be stepping you through creating a smart contract with the Vyper programming language, deploying it to the Ethereum test network, and then interacting with the contract - calling its two externally accessible functions. The contract deployment and contract interaction are achieved using two in-browser tools - MyEtherWallet (MEW) and MetaMask.


Background reading

Before you start I would highly recommend this article to get up to speed with the Ethereum ecosystem as a whole.
https://medium.com/@mattcondon/getting-up-to-speed-on-ethereum-63ed28821bbe
Its from 2017 but the content is still valid today.

Along with the Ethereum whitepaper, which is a must-read if you're a developer:
https://github.com/ethereum/wiki/wiki/White-Paper


Installing the Vyper compiler

There's a number of options available to you as documented here:
https://vyper.readthedocs.io/en/latest/installing-vyper.html

One of the simplest options is to install it using python's package manager (pip). I'll start by ensuring you have the python development environment setup correctly.


Installing a recent version of Python and Pip with Pyenv

If you don't have already have a recent version of Python installed, or don't even know, I would highly recommend that you start by installing pyenv. Pyenv is a tool for python version management.

I followed the Pyenv installation instructions for a MacOS installation via homebrew, but there are instructions for other OSes given.

After installing pyenv, you can install the latest version of python with

% pyenv install 3.8.0

You will now have an up to date version of both python and pip available from your terminal command prompt.

Check what version of python is installed:
% python -V

Output:
Python 3.8.0


Check what version of pip is installed:
% pip -V

Output:

pip 19.2.3 from /Users/hg/.pyenv/versions/3.8.0/lib/python3.8/site-packages/pip (python 3.8)


Installing the Vyper compiler

Once you have python and pip installed aready, you can install the Vyper compiler with:

% pip install vyper

Check what version of Vyper is installed:

% vyper --version

Output:
0.1.0b16+commit.5e4a94a



Writing a smart contract in Vyper

For the first contract, we're going to keep things very simple and go with a public storage contract.
All the contract does is stores a single number in its storage area, and implements two public accessor functions - a getter and a setter.

Use a text editor and open a new file named storage.vy

Note that vy is the file extension for Vyper source code.

Here's the Vyper code:

stored_data: uint256

@public
def set(new_value : uint256):
    self.stored_data = new_value

@public
@constant
def get() -> uint256:
    return self.stored_data


Line 1 declares a variable named 'stored_data' of type 'uint256'. This is the only thing that the contract will store on the blockchain.

Line 3 (def set...) declares the public set function, to update the 'stored_data' variable.

Line 9 (def get...) declares the public get function, to get the 'stored_data' variable.

Its important to note that these two functions get and set are marked public and so can be 'called' by anyone - by using Ethereum client software like MyEtherWallet, or programmatically via the web3 or by other library.

Calling a public function on a smart contract actually consists of sending a transaction to the deployed contract's address - but this is an implementation detail hidden that is hidden by client software or libraries.

Further reading about the structure of contracts in Vyper:
https://vyper.readthedocs.io/en/latest/structure-of-a-contract.html


Compiling the Vyper smart contract

To compile the contract into EVM bytecode:

% vyper -f bytecode storage.vy > storage.bin

To generate the ABI for the contract:

% vyper -f abi storage.vy > storage.abi

The ABI is a JSON format interface specification file, and it is needed to be able to interact with the smart contract (i.e. call its functions).


Deploy the smart contract to the Rinkeby test network

Install MetaMask

If you haven't already, now is a good time to install the MetaMask plugin/extension into your browser - https://metamask.io/
Setup MetaMask with a seed phrase and it will create an in-browser Ethereum wallet that you can use for real transactions, or in this case, test transactions.

When MetaMask is setup, at the top right you should see the network dropdown.

Change this to 'Rinkeby Test Network'. Press the DEPOSIT button, then press the GET ETHER button under the TEST FAUCET option.

After opening https://www.rinkeby.io/#stats use the 'Crypto Faucet' button on the left and follow the instructions. The minimum option (3 ETH) will be more than enough for test purposes and to deploy this contract.

The second thing you'll need to do is to setup MyEtherWallet (MEW). This will let you deploy and interact with your smart contract.


Setup MyEtherWallet (MEW)

Go to https://www.myetherwallet.com and setup the seed phrase.

Now you'll need to give MyEtherWallet (MEW) access to your MetaMask wallet, which contains the 3 ETH for testing.

Go to https://www.myetherwallet.com/access-my-wallet and press the browser extension option.

It should come up with a 'Access via MetaMask' window, and you'll need to grant it permission. Press the 'Access My Wallet' button.


Deploy the smart contract

Inside MyEtherWallet (MEW), press the left hand side option 'Contract' then 'Deploy Contract'.

From the terminal, issue this command:

% cat storage.bin

Output:
0x6100f456600436101561000d576100ea565b600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a0526360fe47b160005114156100c25734156100ba57600080fd5b600435600055005b636d4ce63c60005114156100e95734156100db57600080fd5b60005460005260206000f350005b5b60006000fd5b6100046100f4036100046000396100046100f4036000f3

Using the mouse, copy the contents of the file from the terminal prompt.

Inside MEW, paste the copied text contents into the bytecode field.

On the terminal again, issue this command:

% cat storage.abi

Output:
[{"name": "set", "outputs": [], "inputs": [{"type": "uint256", "name": "new_value"}], "constant": false, "payable": false, "type": "function", "gas": 35315}, {"name": "get", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1181}]


Using the mouse, copy the contents of the file from the terminal prompt.

Inside MEW, paste the copied text contents into the ABI/JSON field.

Enter a name for your contract, e.g. 'storage1'.

Press the 'Sign Transaction' button.

MetaMask will open up a window where you'll be able to confirm the deployment of your contract.

You'll see the gas fee and total.
You can press on the data tab and you'll see the bytecode that you pasted earlier.

Press the Confirm button to proceed with the deployment of your contract.

You may need to wait several seconds for the transaction to be confirmed at this point.
MetaMask should give you a successful deployment popup and link you to the transaction on etherscan.io.
E.g.
https://rinkeby.etherscan.io/tx/0x36663b338ab0eaa7d7cdd91aa5abacdc273757ff56b81221d76a2ff0aedc9860

Here you can see the address of your newly deployed contract. In my case it is here:
https://rinkeby.etherscan.io/address/0x7baad2f634d6bde84916e7d6db40ca2e502eaff6


Side note on contract addresses

Contract addresses are calculated deterministically, based on the account that created it. They are based on the nonce value of your account. So it is possible to know in advance what the contract address will be, if necessary.

Details here: https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed


Interacting with the smart contract

Now that your contract is deployed, its time to interact with it - call its get and set functions.

The get function can be called 'for free' - it doesn't cost any gas, because its just returning the current state of the blockchain.
Calling the 'set' fuction however must be paid for in gas, because by calling it you are actually changing the current state of the blockchain.

Go back to MEW and navigate to the left hand side 'Contract' option then 'Interact with Contract'.

Paste your newly created contract address into the 'Contract Address' field.

In the ABI/JSON field, past your ABI file contents.

Press the Continue button.

There's a dropdown which shows 'Select an item'. This lists all of the public functions on the smart contract. There's only two available - get and set.

Choose get first, it will execute immediately and show the result being 0. 0 is the default value for uninitialised storage in Ethereum.

Now choose 'set' from the dropdown.

Enter the required value into the new_value field, e.g. 88.

Leave the Value in ETH as 0.

Press the 'Write' button.

MetaMask will open up a window where you'll be able to confirm your contract interaction.
What you're actually doing here is sending a transaction to the contract with a value in the data field - the data specifies to the EVM which function is being called (set) and the value of the parameter to it (88).

Press the confirm button.

You may need to wait several seconds for the transaction to be confirmed.

MetaMask should give you a confirmed transaction popup and link you to the transaction on etherscan.io.
E.g.
https://rinkeby.etherscan.io/tx/0x42af3501fff6ddd67a97cc52c94565f149f4d7f985d223523cebae2efc693bbb

Press the "Press to see more" link at the bottom of this page on etherscan.io to see the detail of the function call you made.

Go back to MEW and this time select the 'get' from the dropdown.

Confirm that the value you set is now reflected by the get.

You have now successfully deployed an Ethereum smart contract written in Vyper, and interacted with it using MetaMask and MyEtherWallet (MEW).

Congratulations!


Final notes - can you modify a smart contract?

Note that once a smart contract is deployed, you can't modify it; it is immutable and exists on the Ethereum blockchain forever.

If you need to modify or update the contract, you'll need to deploy the updated code to a new contract address.

There are various techniques for handling contract "upgrades" including migrating the existing storage. There's a good article here on this topic: https://mixbytes.io/blog/storage-upgradable-ethereum-smart-contracts

Note also that a contract can destroy itself (along with its storage), via the selfdestruct() function, as documented here: https://vyper.readthedocs.io/en/v0.1.0-beta.15/built-in-functions.html#selfdestruct

Once destroyed, the contract and its storage are removed from that block onwards. After destruction, it is technically still possible to see the historical contract bytecode and storage, which exists on earlier blocks of the Ethereum blockchain.


Follow @dodgy_coder

Saturday, February 8, 2020

Programming Bitcoin Cash (BCH) with the NBitcoin .NET library and C#

The NBitcoin library is the most active and well supported library in .NET for working with Bitcoin and other similar cryptocurrencies.

NBitcoin fully supports Bitcoin Cash (BCH), however the ebook and programming guide examples focus solely on Bitcoin Core (BTC).

The problem you encounter when following the NBitcoin guide is firstly that the QBitNinja API only supports Bitcoin Core and secondly that broadcasting the transaction seems to require either running a full local node or using the QBitNinja API.

In this tutorial I'll show you how to spend your Bitcoin Cash using the NBitcoin library running under dotnet core, without the need for running a full local node or for accessing the QBitNinja API.

The program you write here will create a new transaction, sign it with your private key, and broadcast it to the Bitcoin Cash network.


Dotnet core
is the preferred programming environment for NBitcoin. Dotnet core is open source and runs under the Linux, MacOSX and Windows operating systems.

Bitcoin Cash is the ideal cryptocurrency for experimenting with programming APIs and libraries - you can send small transactions for very low fees, typically for a fraction of one US cent.


Step 1: Project Setup

Follow the setup guide as normal:
https://programmingblockchain.gitbook.io/programmingblockchain/introduction/project_setup

The rest of these steps 2-12 below loosely follow the "Spend Your Coin" guide:
https://programmingblockchain.gitbook.io/programmingblockchain/bitcoin_transfer/spend_your_coin

Its not absolutely required, but for a more complete and in depth understanding, I would recommend you read through the "Spend Your Coin" guide above (with the knowledge that it applies to Bitcoin Core) before proceeding with the rest of the below steps.


Step 2: Add support for Bitcoin Cash

On the command line:

dotnet add package NBitcoin.Altcoins

The code - add these using statements to the top of the C# source file.

using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Protocol;


And at the beginning of the program, add this:

// Important Note - this is not test code.
// It is going to run in production (called the "mainnet").
var network = NBitcoin.Altcoins.BCash.Instance.Mainnet;



Step 3: (Optional) Generate a new private key and address

The below code snippet generates a new BCH private key and BCH address using the NBitcoin library.
  
// Generate a random private key.
Key rawPrivateKey = new Key();

// The private key, also known as the Bitcoin secret or the WIF (Wallet Interchange Format).
// If you intend to use it, make sure you save the below somewhere safe!
BitcoinSecret privateKey = rawPrivateKey.GetBitcoinSecret(network);
Console.WriteLine("privateKey = " + privateKey);
Console.WriteLine("address (from privateKey) = " + privateKey.GetAddress(ScriptPubKeyType.Legacy));


Alternatively you can generate a new private key and address using BCH wallet software.


Step 4: Setup the variables for your private keys and addresses

To spend your Bitcoin Cash, you'll need to know the private keys of both the "destination" BCH address and the "from" BCH address.
The "from" BCH address is also known as the input address or the outpoint to spend.

Here's the code to setup the address and private keys of both the "destination" address and the "from" address.

// Destination - private key and address. The destination of the new transaction we are creating.
// Note: its not strictly necessary for creating the transaction to have the private key of the destination,
// but its better to add it here so that you know that you have access to it, and that the money won't be 'lost'.
var privateKey = new BitcoinSecret("PASTE_YOUR_DESTINATION_ADDRESS_PRIVATE_KEY_HERE", network);
Console.WriteLine("dest privateKey = " + privateKey);
var address = privateKey.GetAddress(ScriptPubKeyType.Legacy);
Console.WriteLine("dest address = " + address);
Console.WriteLine("dest address scriptPubKey= " + address.ScriptPubKey);

// Input Transaction - private key and address. The from address.
var inPrivateKey = new BitcoinSecret("PASTE_YOUR_FROM_ADDRESS_PRIVATE_KEY_HERE", network);
var inAddress = inPrivateKey.GetAddress(ScriptPubKeyType.Legacy);
Console.WriteLine("inPrivateKey = " + inPrivateKey);
Console.WriteLine("inAddress = " + inAddress);
Console.WriteLine("inAddress scriptPubKey= " + inAddress.ScriptPubKey);


You can run the program now to make sure its working - on the command line:

dotnet run


Step 5: Determine the Transaction ID of the "from" address

Now you need to determine the transaction ID and index number of your "from" address by looking at the transaction in a blockchain explorer and plugging in the correct TxId and index number below.

// Determine the previous output that will be spent, as the Input to our new transaction.
// For example:

// https://explorer.bitcoin.com/bch/tx/292f70e71f8bc2e94b7c0ac46c4e89371dc59b821639de67376f6f0b09544d92
string txInIdString = "292f70e71f8bc2e94b7c0ac46c4e89371dc59b821639de67376f6f0b09544d92";
uint txOutIndex = 0; // <== Ensure this index is correct!
OutPoint outPointToSpend = OutPoint.Parse(txInIdString + ":" + txOutIndex);



Step 6: Create the new transaction

var transaction = Transaction.Create(network);
transaction.Inputs.Add(new TxIn()
{
    PrevOut = outPointToSpend
});



Step 7: Setup the amount to spend and the miner fee

// Suggested miner fee for this transaction, as of February 2020,
// is 0.000003 BCH or around USD 0.001 (a tenth of a cent).
// Check recent blocks for guidance.

var minerFee = new Money(0.000003m, MoneyUnit.BTC);  

// Replace below with your amount.
// It is the total unspent amount of the "from" address.
var txInAmount = new Money(0.0021m, MoneyUnit.BTC);

// Move to the destination address.

var spendAmount = txInAmount - minerFee;
transaction.Outputs.Add(spendAmount, address.ScriptPubKey);


Step 8: Add a message to the transaction, using the OP_RETURN template (max 80 characters)

var message = "Test 1: using the NBitcoin library to move Bitcoin Cash. BCH Rocks!";
var bytes = Encoding.UTF8.GetBytes(message);
transaction.Outputs.Add(Money.Zero, TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes));



Step 9: Sign the transaction

// Get the ScriptPubKey from the private key of the outPointToSpend.
transaction.Inputs[0].ScriptSig = inAddress.ScriptPubKey;

// Sign the transaction with the input private key.
var txInId = uint256.Parse(txInIdString);
var inCoin = new Coin(txInId, txOutIndex, txInAmount, inAddress.ScriptPubKey);
transaction.Sign(inPrivateKey, inCoin);



Step 10: Determine the TxId of your transaction, which is just its hash

Console.WriteLine("New TxId: " + transaction.GetHash());

Now is a good time to run the code and check its all working. From the command line:

dotnet run


Step 11: Broadcast your transaction to the Bitcoin Cash network

This is the final step and should only be attempted if your program runs fine up to step 10.

Before you run it, confirm your amount to spend is correct.

//
// Connect to a BCH node and broadcast the transaction.
// See here for the DNS Seed list:
// https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/src/chainparams.cpp

//
using (var node = Node.Connect(network, "seed.bchd.cash:8333"))
{
    // Say hello to the node.
    node.VersionHandshake();

    // Advertise your transaction (send just the hash).
    node.SendMessage(new InvPayload(InventoryType.MSG_TX, transaction.GetHash()));

    // Send the contents of the transaction.
    node.SendMessage(new TxPayload(transaction));

    // Wait for the message to be sent.
    Thread.Sleep(5000);
}


Run your program the command line:

dotnet run


Step 12: That's it, well done for making it to the end!!

If everything went well, your new transaction was accepted by the Bitcoin Cash network, and it will show up in a block explorer such as this one:

https://explorer.bitcoin.com/bch/tx/3ecf0f0dafd26e8d788b59a433bf00e1c3c079e82dada5111e899c87a75dadff

Replace the above TxId with yours from step 10.

Your new transaction will be listed initially as having 0 confirmations, which means its in the mempool.

Now you just need to wait for your transaction to be confirmed. This will happen as soon as the next block is mined and your transaction gets added to that block by the winning miner.


Follow @dodgy_coder

Tuesday, October 1, 2019

Counter overflows and clock drift bugs in aircraft and missile defense systems

A counter overflow bug happens when an unsigned integer variable storing a counter reaches its maximum value; when its already at the maximum, as soon as one more value gets added to it, the variable will reset back to 0 and continue counting up from there. If the rest of the software is not expecting this counter reset, it can result in system failures. So the longer the system is running for, the nearer the system will get to a counter overflow event.

Clock drift bugs happen when an internal timer/clock gets calculated incorrectly, causing it to slowly drift out of sync with real time. The longer the system is running, the more the clock drifts and the bigger the error gets. When real time clocks are out of sync, all sorts of downstream calculations can become inaccurate.

Both of these types of bugs typically have a workaround that requires the operator to reboot the system every X hours or days.

Both counter overflows and clock drift bugs can occur in production systems when system testing didn't include a "soak test" - where you keep the system running for a very long period, typically measured in days rather than hours. The failure to pick up the bug during system testing means that a production system gets deployed with a bug, and the only workaround becomes a regular reboot of the system.

The kicker - these type of bugs have been happening in safety critical systems for at least the past 28 years

One of the most publicized cases was the failure of a Patriot missile defense system in 1991, in which the system failed to track an incoming Scud missile, due to a drifting and out of sync clock. Tragically, this particular failure resulted in the death of 28 U.S. soldiers based at a U.S. barracks near to Dhahran, Saudi Arabia. The workaround for this bug was to reboot the system every 8 hours, but unfortunately this had not been communicated to the base in time to avoid the disaster.

Another well known case was the counter overflow in the Boeing 787 Dreamliner firmware (2015), which meant the aircraft had to be rebooted once every 248 days to avoid a counter overflow bug.

The most recent case was an internal timer bug in the Airbus A350 firmware (2019), which requires that the aircraft be rebooted once every 149 hours. 

Avoiding bugs in safety critical systems  

Avionics systems are among the most complex things ever built by man. There's one leading example of a successful avionics project which was built from scratch targeting high quality and zero defects. Its the Boeing 777 - Boeing's first fly-by-wire aircraft.
For the 777, Boeing decided early on to standardize on the Ada programming language, at a time when using C was the norm. Compilers for Ada had been certified as correct, and the language itself included several safety features, such as design by contract and extremely strong typing.

Ada itself came about as a way to standardize all of the programming languages in use by the U.S. Department of Defense - before Ada they were using some 450 different programming languages. Ada was originally designed to be used for embedded and real time systems.

Ronald Ostrowski, Boeing's director of Engineering, claimed that the Boeing 777 was the most tested airplane of its time. For more than 12 months before its maiden flight, Boeing tested the 777's avionics and flight-control systems continuously - 24hrs - 7days - in laboratories simulating millions of flights. 

The 777 first entered commercial service with United Airlines on 7th June, 1995. It has since received more orders than any other wide-body airliner. The 777 is one of Boeing's best-selling models; by 2018 it had become the most-produced Boeing wide-body jet, surpassing the legendary Boeing 747.

Further reading - Boeing flies on 99% Ada

Follow @dodgy_coder

Saturday, September 28, 2019

Review of Shopify - from a developer's point of view

I setup this online shop over a couple of weekends - Chow Slow - Slow Feeder Dog Bowls. After launch I continued to make various improvements and optimisations to the presentation and content of the site. Since launch three months ago, the online shop has so far taken 72 orders.

The eCommerce SaaS product Shopify was used to build the site, on the Basic Shopify (monthly) plan.

Here below are my opinions on Shopify:

Shopify - Good points

  • Many payment options - I chose the Shopify Payments option. It accepts Apple Pay and Google Pay when on a mobile browser for example. You can tell that Shopify have extensive experience with taking payments - everything is really polished.
  • Test mode works well for putting through test credit card orders and seeing the customer experience end to end. Can also password protect the site while its under development.
  • The free themes are good and can be customised for most online shops. I used the free "Minimal" theme and then customised the layout of the home page, added several pages and added a blog.
  • Great optimisations and layouts which work well on mobile. More than 60% of the traffic for the site comes from a smartphone browser as opposed to a traditional desktop browser.
  • Really useful "Timeline" admin feature on the customer and order admin pages - see all the interactions that have happened with a customer in a simple timeline display, with a timestamp shown on each interaction.
  • Excellent "Conversion Summary" admin feature - see the site behaviour of your customer from when they first visited your site to when they bought something.
  • Comprehensive "Fraud Analysis" admin feature - for orders paid by Credit Card - recommendation of whether the transaction is likely fraudulent or not: probability shown as low, medium, or high.
  • Ability for customers to sign up with either their mobile phone number or email address. Shopify automatically sends communications to the customer using either mobile SMS or by email.
  • Easy to add social media accounts, which then automatically appear on the site footer.
  • Products are easy to configure and easy to change in terms of page title and content.
  • Ability to edit raw HTML in most places.
  • Easy to setup a custom domain, with TLS/SSL included (which just works).
  • There are SEO optimisations and settings available at a page level. You can change the page title, meta description and URL.
  • Integration with Google Analytics is excellent - the 'Advanced eCommerce' option should be turned on - it results in a data going straight into GA for your conversions and sales.
  • Page load time is good - bulk of the home page loads typically in 2-3 seconds.
  • A comprehensive API for writing custom integrations with other backend software and systems.
Shopify - Bad points
  • Marketing integrations - Facebook Ads for example - difficult and error prone to setup. Facebook Ads never worked for me after many headaches trying to setup. The marketing integrations seem like a quick way to lose money fast.
  • Inability to structure the Products, Pages and Blog sections to be within a custom sub-folders structure (as is typically recommended to silo a website for SEO purposes).
  • Checkout page customization is limited to Shopify Plus users.
  • Checkout script editor is limited to Shopify Plus, so any custom logic to apply discounts has to be done through plugins.
  • API is rate limited - will be fine for most users however ERP systems which run via batch processing or via polling may have issues. You need to implement everything as event driven webhooks rather than by polling the Shopify API.
Shopify - Areas for improvement
  • I'm not convinced of the need for many of the Shopify apps (plug-ins) that get automatically suggested. I have a feeling that they will result in bigger page sizes and slower load times. I have not needed to use any apps yet.
  • Its sometimes hard to find the relevant place for a setting - whether under preferences of the online shop or under settings of the account itself. I should probably make more use of the search bar at the top for this in the future.
Shopify - Conclusion

I highly recommend Shopify for setting up an online shop. When launching, your focus will be taken up with gaining your first customers, online marketing and the delivery process, so having a solid full featured website behind you is crucial.




A bit more about our online shop - Chow Slow - Slow Feeder Dog Bowls

I setup the site for my wife, who runs the business day to day. She handles the processing of orders and deliveries. We came up with the idea of selling to this niche market after finding out about this type of product in 2018. Our pet beagle (Ripley) had an issue of regular regurgitation due to eating too quickly. The slow feeder bowl product solved his problem and results in more relaxed meal times every day.



Follow @dodgy_coder

Thursday, April 18, 2019

Inactive/Failing Mobile Apps: The Many Benefits of Regular App Updates

This article applies mainly to mobile apps which are not in active development. It applies equally to mobile apps on the public app stores and also to enterprise mobile apps which get distributed via other means (such as MDM software).

Example scenario of an inactive/failing app:
  • The active marketing of your app that happened at launch has now stopped
  • Your app's last update was several months ago
  • The uninstallation rate has started rising above your installation rate month on month

A way to prevent this happening is to release regular app updates, even when there are no major changes to your app's code.



Benefits of releasing regular app updates:

  • Gain new users; many users look at the last release date on the app store page before downloading your app. Anything over a year old can be immediately rejected as out of date.
  • Prevent uninstalls; seeing your app has been updated on their phone keeps it in the user's mindshare, so they know you are still actively developing it.
  • Update the dependencies; you will likely have several dependencies on open source libraries and mobile SDKs. The more often you release, the more often these can be updated too.
  • Update the toolchain; Xcode / Android Studio / Visual Studio get updated once every month or two. Same applies to hybrid and cross platform toolchains such as Unity, Ionic and Xamarin.
  • Update the store listing; since you're doing a build, take the opportunity to review and tweak the store listing and screenshots. Apple's App Store Connect website and Android's Google Play Console get updated regularly with enhancements you can take advantage of.
  • Less build issues; there will be a lower chance of a major build issue if you regularly update your toolchain and its dependencies. In my experience, the longer you wait between app builds and releases, the greater the chance of a major build issue the next time.
  • Source control change visibility; the project is kept active in terms of source control history; especially relevant if its an open source project with other potential developers able to see the change history on github or bitbucket.
  • Developer benefits; you don't end up with a legacy mobile app that noone can get building anymore.

What if there are no significant changes to your code?
  • Have the dependencies or toolchain been updated? If yes, that means your app has gained some potential bug fixes and enhancements for free - just by doing a build.
  • There's always some minor refactoring you can do to make small improvements to your codebase. Just spend a couple of hours if that's all you can spare.
  • The release note; just use the standard "Bug fixes, performance and stability improvements" if you can't think of anything else.

How often to release?
  • The more often the better.
  • Aim for a regular release cycle of between 1-3 months.

Further reading

Distribution of Android apps per download range
https://www.statista.com/statistics/269884/android-app-downloads/

Download distribution of Android apps, and lifetime growth rates per download range
https://www.appbrain.com/stats/android-app-downloads

Statistics about the release schedule of apps on the Apple app store
https://stories.appbot.co/how-often-should-you-update-your-app-9405b85a967c

Best practices for Apple app store updates
https://developer.apple.com/app-store/app-updates/


Follow @dodgy_coder

Subscribe to posts via RSS


Sunday, September 18, 2016

Lightweight IoT Command and Control

Many hobbyist IoT projects running on the Raspberry Pi (RPi) require a webserver running on the RPi which receives requests from a browser, where the client using the browser is connected either to the local WiFi network, or possibly (via a port forwarding setup), connected to the public Internet.

An alternative that is easy to setup, lightweight and low cost is one that makes use of Amazon's SQS (Simple Queue Service) to setup two queues. One queue is for commands - from a mobile application to the RPi. Another queue is for responses - from the RPi back to the mobile application. It results in a simple and easy to secure solution available over the public Internet. It requires no port forwarding - both endpoints make use of a client connection to Amazon AWS. Given the small amount of messages needed for typical hobbyist project, the SQS component will be covered by the AWS free tier.

Here's the high level design of the system ...

Lightweight IoT Command and Control
On the Raspberry Pi, a lightweight application is running that makes use of the excellent Python boto3 AWS SDK library. It is always waiting for a command to arrive in the command queue. As soon as it receives a command, it processes it and adds a response to the response queue.

On the mobile device an Android application is running that makes use of the AWS Mobile SDK for Android. When the user presses a command button, such as 'Open Garage Door', it adds a command to the command queue, and asynchronously waits for a response to arrive in the response queue. When it receives a response (e.g. the command's success or failure status), it updates the app's user interface.

Saturday, January 2, 2016

Custom domain for Azure Web Apps using FreeDNS

The below steps go through setting up a custom domain name on your Azure Web App, using the FreeDNS service from NameCheap. Both the www subdomain www.yourdomainhere.com and the naked yourdomainhere.com will be setup.

I have an Azure S1 Small Instance App Service Plan on which I'm running 4 web apps. Originally I had planned to use Azure DNS, to keep everything within the same cloud service, but after some initial attempts formed the opinion that Azure DNS isn't mature enough yet to host production web sites. Azure DNS can currently only be administered from within Azure Powershell - that in itself was enough to put me off using it. If you need to make a quick change to a DNS record, you don't want to be having to run Powershell to do it. Microsoft are working on adding Azure DNS to the Azure Portal, but at the moment it's not available.

I have a couple of domains through NameCheap and for those and a couple of others (with CrazyDomains) I decided to use NameCheap's DNS service.  Even when your domain is not registered through NameCheap, they still offer a free DNS service, called FreeDNS.

Note that in the below steps, yourwebappname and yourdomainhere.com should of course be replaced with your specific names.


Step 1. Reduce the TTLs to 5 minutes

Note: you only need to perform this step if you're porting from an existing DNS provider. No need to do this if you're setting up a new web app on a new domain.

Within your current DNS service, setup all the TTL settings (Time To Live) on the DNS records to the minimum (typically 300 seconds, or 5 minutes). This may help in having the DNS changes propagate faster.

After this is done, try to get a full DNS zone file listing. When you port the DNS records to FreeDNS it will help ensure that you don't miss anything. My previous VPS and DNS provider was Linode and I was porting a few web apps from Linode over to Azure web apps.


Step 2. Ensure your Azure web app is running

Ensure your web app is published and running ok at http://yourwebappname.azurewebsites.net


Step 3. Find the IPv4 address of your Azure web app

From a windows cmd.exe prompt ...

C:\> nslookup yourwebappname.azurewebsites.net

Server:  UnKnown
Address:  192.168.1.1

Non-authoritative answer:
Name:    waws-prod-xxx-xxx.cloudapp.net
Address:  1.2.3.4
Aliases: 
yourwebappname.azurewebsites.net
          waws-prod-xxx-xxx.vip.azurewebsites.windows.net


The nslookup command will provide you with your web app's IPv4 address. This is shown in the second Address field above, e.g. 1.2.3.4. I am running an Azure S1 Small Instance App Service Plan. I believe this method still applies to other plans including shared instance plans.

Note that if your web app is restarted or stopped this may result in it being assigned a different IP address. Be aware of this when using naked domain URLs (without the www prefix) since these rely on the A record being configured for the correct IP address of your web app (as per step 6).


Step 4. Setup FreeDNS

On the NameCheap FreeDNS page, enter yourdomainhere.com and click 'Get DNS'. If its eligible, then click 'Add to Cart', then click 'Set up DNS'.

You get this message from NameCheap :-

N.B. don't go ahead and set the nameservers just yet, that step will come later.

    yourdomainhere.com
    Congratulations! Your domain/ sub-domain is added to our DNS service.
    Please set the nameservers of your domain/ sub-domain to

        freedns1.registrar-servers.com
        freedns2.registrar-servers.com
        freedns3.registrar-servers.com
        freedns4.registrar-servers.com
        freedns5.registrar-servers.com

    Our system will periodically monitor your domain's DNS setting and will activate your domain once it is pointing to our servers.

   

Step 5. Verify ownership of your domain

In NameCheap, on the Domain List page, once the yourdomainhere.com is listed as active, then need to click on "Authorize DNS" link...

Select the required email, such as admin@yourdomainhere.com to use as the auth email.

On the domain page, the redirect domain setting will show "Your FreeDNS domain is waiting for Authorization or Verification by domain owner."

In the received email, click on the embedded hyperlink and then on the webpage, click the "I AUTHORIZE" hyperlink.

A message should display "Host has been successfully activated".


Step 6. Setup the FreeDNS records

At this step you'll setup all of the DNS records for your domain.
For all of these I left them with the default TTL setting of 'Automatic'.
In NameCheap, on the Domain List page, click the MANAGE button next to your domain, then click on the Advanced DNS tab.

In the Host records, add the A record with the IP that was returned in step 3.

Type  Host  Value            TTL
A     @     1.2.3.4          Automatic


Add any required TXT records (often used for verification).

Add the 3 CNAME records required to verify ownership of the domain to Azure.

1. Set HOST to awverify
    Set Target to awverify.yourwebappname.azurewebsites.net

2. Set HOST to awverify.www
    Set Target to awverify.yourwebappname.azurewebsites.net

3. Set HOST to www
    Set Target to yourwebappname.azurewebsites.net
  
If required, set the mail setting to Custom MX and add the MX server records...
For example if you have Gmail enabled on the domain you'd use these MX records...

Set Mail to Custom MX

Host        Type Priority      Mail server name
@            MX     20         ALT1.ASPMX.L.GOOGLE.COM.
@            MX     10         ASPMX.L.GOOGLE.COM.
@            MX     20         ALT2.ASPMX.L.GOOGLE.COM.
@            MX     30         ASPMX2.GOOGLEMAIL.COM.
@            MX     30         ASPMX3.GOOGLEMAIL.COM.

  
  
Step 7. Change the DNS nameservers using your registrar

For the case when your registrar is not NameCheap, you'll need to point the nameservers for the domain to the FreeDNS nameservers.

Log into your registrar's website and set the following nameservers, removing the existing nameservers if required ...

        freedns1.registrar-servers.com
        freedns2.registrar-servers.com
        freedns3.registrar-servers.com
        freedns4.registrar-servers.com
        freedns5.registrar-servers.com



Step 8. Wait for the DNS nameserver change to propagate

You can check whether the changes have propagated yet using nslookup from the windows command line ...

C:\> nslookup yourdomainhere.com
C:\> nslookup www.
yourdomainhere.com
C:\> nslookup awverify.
yourdomainhere.com
C:\> nslookup awverify.www.
yourdomainhere.com

Use nslookup in interactive mode for MX and TXT record lookups ...


C:\> nslookup

set q=mx

yourdomainhere.com

set q=txt

yourdomainhere.com

exit


Or you can also use these great online tools for checking the DNS propagation ...

    http://www.whatsmydns.net
    https://www.ultratools.com/tools/dnsLookup

Or on Linux I believe the command is: dig yourdomainhere.com

   
Step 9. Bring the domains into your web app using the Azure Portal

In the Azure portal, go to yourwebappname Web App -> Settings -> Custom Domains and SSL -> Bring External Domains

Enter yourdomainhere.com into the field, and then press tab. Wait for it to verify your CNAME records.

In the next field, enter www.yourdomainhere.com and press tab again. Wait for it to verify your CNAME records.

It should succeed as you tab out of each field, and after you click the save button at the top, it should report "Updating hostname bindings".


Step 10. Test in a browser

Open both www.yourdomainhere.com and yourdomainhere.com in a browser and confirm that they load ok.

Turn on the browser's web developer tools (Network tab) to check that all the components of the web app are loading with HTTP 200 OK status, and are resolving to the new IP address.

Its normal to experience some issues for the first several hours after changing nameservers, for example you may see the domain resolve to the old name server settings or flipping back and forth from new to old. Usually this will settle down after a few hours. It can be caused by the browser caching DNS results, something I've noticed particularly bad in Firefox sometimes. You may also need to flush the DNS cache on the computer you are using.


Flush the DNS cache in windows via:

C:\> ipconfig /flushdns


Other references ...

There is some additional Azure related information in the below post about this subject:
https://azure.microsoft.com/en-us/documentation/articles/web-sites-custom-domain-name/



Follow @dodgy_coder


Subscribe to posts via RSS

Sunday, December 13, 2015

Low cost airlines online pricing hacks

Low cost airlines are the masters of dynamic pricing, bundling and pricing psychology.

Every customer who makes an online booking presents them with some new data to plug into their dynamic pricing algorithm. They also analyse the best way to present the various upgrades available to purchase before the final checkout, to maximise the customer's spend.

I've just taken a look at budget carrier JetStar's booking experience and its use of various pricing techniques and psychology.

After you've searched and selected your flights you'll be presented with a choice. Look at how unattractive they make the "Starter fare" default option ...


The "Plus bundle" at $106 is the one JetStar wants you to pick. The "Max bundle" at $753.49 is just there as an anchor price, to give you the impression that the middle one is good value. The "Starter fare" is the one they want you to avoid. They do this through the selective use of negative and positive language and the colours of grey, green and blue.

The Starter fare

Comfortable leather seat
This is patronising ... is there so little going for this bundle option that they have to mention that you actually get a seat, as opposed to standing in the aisle?

7kg carry on baggage
Strict size and weight limits apply
Negative tone. 7kg is not enough for most people's needs.

Checked baggage not included
Repeated, negative tone.

Starter fares are non-refundable
Negative tone.

... compare this to the most popular option, the option JetStar wants you to choose ...

The Plus bundle

No change fees
Positive.

2,225 Qantas points
Free money. Only $30 credit off your next fare, but its better than nothing.

Free standard seat selection
The power of free.

Meal
More included stuff.

20kg checked baggage
Notice they don't mention that a strict size and weight limit also applies to the 20kg baggage.


People are scared of change fees

Most people when seeing the starter fare option will get the impression that if they need to change their flight for any reason, perhaps due to a flight delay, they are in trouble. In fact, JetStar covers you for flight delays and will make the necessary arrangements and additional bookings free of charge. But they only mention this in the fine print.

Most people want checked baggage

You also get the impression that you have to choose the plus bundle or max bundle if you want checked baggage. Actually, you can choose the starter fare, and then later on in the process you can optionally pay for only the specific amount of checked baggage that you need, at a rate of $46 for 15kg, $50 for 20kg and $62 for 25kg. Most customers likely don't know that these cheaper options are available, and will just choose the plus bundle, paying $106, when all they needed was checked baggage. You can select the Starter fare, then pay separately for 20kg checked baggage ($50), a meal ($15) and a reserved seat ($6) and the total will be just $71, a saving of $35 on the Plus bundle.

Noone wants to be randomly assigned a seat

When it comes to selecting a seat, its a bit disingenuous for JetStar to claim this ...

"You have not selected a seat. If you do not wish to pre-select a seat one will be assigned for you randomly from what is still available at check-in."

When you check in at many airports, assuming you get to the airport early and the plane isn't sold out, you'll still be able to select what type of seat you want - the check-in staff are usually more than helpful and should be able to provide your choice of a window or an aisle seat.

If you're a family with children, the airline will try to assign an adult family member to be sitting close to each child in your family, even when you didn't pay to reserve a seat. If you can't be accommodated at the point of check in, you would likely be able to work something out after boarding by speaking to the cabin crew.

Also, remember this inconspicuous bit of writing at the bottom ...

"Jetstar will attempt to accommodate your seat preference, however due to operational considerations cannot guarantee that your seat allocation will be as your selected preference."

So in other words, they ask you to pay for a seat preference, but cannot guarantee it.


Follow @dodgy_coder

Subscribe to posts via RSS

Monday, June 15, 2015

The Default Files Folder Pattern

Software installation best practices

When you've got critical settings files that absolutely should never be overwritten or cleared back to defaults, this pattern has always worked well for me. It can apply to both Windows, Linux and Mac based software.

The installation method can either be a simple file copy utility/archive or something more complex like a Windows installer.

An example target structure might look something like this:

/MyApplication
    /bin           => Application binaries.
    /defaults      => A default version of all
settings files.
    /docs          => Documents or manuals.
    /settings      => Critical settings files.

             
The settings folder contains the critical settings files and is not part of the installer. The installer includes only the bin, defaults and docs folders. So installing the software only overwrites bin, defaults and docs, but never the settings. This applies equally to Windows installer packages - for this case, just don't include the settings folder in the Windows installer package. The defaults folder contains the default version of all of the the files expected in the settings folder.

Whenever the software accesses a particular file in settings the following lazy initialisation logic is used ...

- Does the settings folder exist?
    - Yes: Continue
    - No:  Create the folder and continue
   
- Does the file exist?
    - Yes: Open it
    - No:  Copy the file from the
defaults folder into the
           settings folder, then open it

By following the above logic, you ensure that an existing critical settings file is never overwritten.

From reading the latest report on the deadly Airbus A400M crash, its claimed that the cause was that their software installation procedure did the equivalent of overwriting a settings file or clearing it back to defaults. In this case, the settings file contained calibration parameters that were critical to the operation of 3 out of the 4 propeller engines.

Follow @dodgy_coder

Subscribe to posts via RSS

Saturday, May 2, 2015

Taking control of a 36 year old NASA spacecraft using GNU radio

In 2014, Dennis Wingo and Keith Cowing formed the ISEE-3 Reboot Project, a crowdfunded effort to attempt to gain control of the decommissioned spacecraft for the benefit of citizen science. The team raised almost $160,000 in funding and assembled top space experts for the cause. In May, 2014, they began communicating with the spacecraft in advance of its August 10th 2014 lunar fly by.

Here's an excerpt of a brilliant podcast interview between software defined radio (SDR) specialist, Balint Seeber, and Infosec journalist, Patrick Gray, as recorded on April 24, 2015. Its the best anecdote about technology and hacking I've heard recently.

Below transcript starts at the [47m:43s] mark of the Risky Business podcast #363
.

Balint: It was also to set a precedent for citizen scientists from the public to essentially take over missions that ... all the missions that NASA may not want to spend its resources, its precious, you know resources and budget on. So this was supposed to be a good first example of how they could do that hand-off to a public group, and there were initial negotiations there and a Space Act agreement was signed so it was all above board and legitimate.

We realised the reason they got in touch with Ettus Research and then with me and my former colleague, John Malsbury, who funnily enough now works at SpaceX, so he's still in the space game, but he and I met with the guys and they basically told us the reason why we need your expertise is because NASA has thrown out all of the old equipment they used to speak to the space probe. So they put the space probe in this graveyard orbit, because the mission had finished and they ran out of funding, but there was no way for the NASA deep space network to actually send it commands to wake it back up again.

And so, having done their techno-archeology, as they like to put it, retrieving all these old NASA documents, we could see the various protocols that would be necessary to re-implement, using software defined radio, in this case GNU radio, to achieve this, you know, recreate the modems to talk to it. And so we did that and we were fortunate enough to be able to go to the Arecibo radio telescope in Puerto Rico, hook our software defined radios up to their big, big telescope, and then send these commands out to the probe, which at the time I think was 15.5 million kilometres away, travelling towards the earth at about four kilometres a second ...

Patrick: That's a long distance call...

Balint: Its a very long distance, and I call it "not your average radio link budget". But we managed after some initial attempts to have some success there and send the commands out to turn the telemetry on. We could then assess the health of the space probe and ...

Patrick: And you did your happy dance of course when the response came back?

Balint: That's true, yeah, we had our unmodulated carrier suddenly become modulated telemetry and it was the first real major milestone of the project. All that preparation that we'd done, all the interpretation of the documents and taking into account the various permutations of parameters and ...

Patrick: And the plan of course was to fire the thrusters in a way to get it into a stable orbit and they you guys could use the various sensors and things on it to quote "Do Science", uh, but it didn't quite work out did it?

Balint: No, unfortunately the grand idealist game was to reactivate all of the operational science instruments on board and actually bring it back and do proper, public science with it, and, you know, have it available for STEM and so on, but unfortunately when we tried to fire the thrusters, in all the different configurations, because there were lots of redundancies in the propulsion system, we never observed any impulse being registered on the accelerometer and the accelerometers data was being transmitted back on the telemetry and it would always sort of flat line. And this was a big disappointment for us, no matter what we tried we just couldn't get the thing to move, and one of the running theories is that the (fuel) tanks were actually pressurised with nitrogen so that it would force the fuel out, back into the rest of the propulsion system, as in the valves and the thrusters, and you could imagine that over the three decades there might have been a very, very slow leak and that nitrogen pressure, it might have just leaked out over time and unfortunately now its left us with an inoperable propulsion system.

Patrick: What, no backup fuel pump? (sarcastic)

Balint: Can you believe it?

Patrick: Bloody American engineering.

Balint: What were those NASA boffins thinking? (laughing)

Patrick: But you were able to actually fire up the sensors and do some science with it. What was the total budget of this project, that was crowdfunded in the end?

Balint: They used Rockethub and I think it was on the order of $150,000, so it was quite a nice little collection there, and yeah, some of the science instruments were reactivated and we actually for a short period got some good science data out of it, which was great, and it was quite funny, when I was at DEFCON last time, I got the call that one of the science instruments needed to be rebooted and I had been waiting for quite a long time in line to get a good seat and there were a number of back to back presentations that I was ...

Patrick: Excuse me I'll be back I've just got to go give the three finger salute to a satellite

Balint: Well that's the thing though, I didn't want to leave and so, unfortunately I didn't have any cell reception so I couldn't tether to my phone, you don't get on the WiFi at DEFCON obviously because then you open yourself up to being hacked. So there's a guy next to me that I started to talk to and we're talking about software defined radio and I sort of sized him up, he was from Norway and he seemed like a good bloke and I asked whether I could tether to his phone. He said yes because he had a prepaid account with some Internet credit on there. And so I ended up tethering to his phone, SSH-ing from the third row of DEFCON into the laptop at Arecibo to send commands to the space probe that was about to fly past the moon, to reboot the science instrument on board, and then I could continue watching the presentation, so it was quite fun.

Patrick: Its a pretty amazing time isn't it...

Balint: Well it just goes to show you how interconnected the entire world is, I mean you've got the WiFi from my laptop to the phone, LTE from the phone to the cellular network, and then the data connection obviously across that and then down to Arecibo and then our custom link to the space probe. So it was a good testament both to SDR and commercially deployed wireless standards.


Futher information ...
Communicating with a space probe using Software Defined Radio

... a video of a presentation Balint gave to the Manly Warringah Radio Society ...


Follow @dodgy_coder

Subscribe to posts via RSS

Saturday, February 28, 2015

Setting up a Bonjour (Zeroconf) service name for your Raspberry Pi and accessing it from an Android App

I have written an Android app that communicates to my Raspberry Pi over SSH (using JSch) but the issue is that in the App the IP address of my Raspberry Pi has been hardcoded at 192.168.1.109 - ideally it would be good to be able to give it a local name, something like garagedoor.local - something that won't change when the DHCP assigns a different IP address.

It turns out that the way to do this isn't difficult on the Raspberry Pi and most of work is in changes to the Android App needed to make it happen. I've numbered the steps 1-4.

1. Configuration of your Raspberry Pi.

# Get superuser privileges
sudo su

# Change the host name of your Raspberry Pi from "raspberrypi" to "garagedoor"
# Reference: http://www.howtogeek.com/167195/how-to-change-your-raspberry-pi-or-other-linux-devices-hostname/

sudo nano /etc/hosts            # <== Replace raspberrypi with garagedoor
sudo nano /etc/hostname         # <== Replace raspberrypi with garagedoor

sudo /etc/init.d/hostname.sh    # Commit the changes.
sudo reboot                     # Reboot

# Install the mDNS implementation - avahi
# Reference: http://www.howtogeek.com/167190/how-and-why-to-assign-the-.local-domain-to-your-raspberry-pi/
# Get superuser privileges
sudo su

# Update the package sources
apt-get update

# Ugrade the packages
apt-get upgrade

# Install the mDNS implementation - avahi
sudo apt-get install avahi-daemon



That's the end of the changes needed on your Raspberry Pi.

2. Test on either Windows, Linux or Mac OSX that the Raspberry Pi is visible on the network with its new name of "garagedoor.local".

On Windows, you will need to have Bonjour Networking Services installed, which comes bundled with iTunes.
Run a cmd.exe prompt and type the command ping garagedoor.local -- you should get a response from your Raspberry Pi.

On Mac OSX, Bonjour Networking Services are installed by default.
In a bash terminal window, type the command ping garagedoor.local -- you should get a response from your Raspberry Pi.

On Linux, first ensure you have installed the avahi-daemon package.
In a bash terminal window, type the command ping garagedoor.local -- you should get a response from your Raspberry Pi.

3. Using NSD (Network Service Discovery) on Android to resolve the Raspberry Pi's service name.

Browsers and terminal programs on Android won't be able to resolve the garagedoor.local since to do this requires use of either the official Network Service Discovery library (supported by API Level 16 up) or the open source Java Zeroconf library named jMDNS.

In my case, I am using a custom app I wrote to communicate to the Raspberry Pi, so I have decided just to use the Network Service Discovery library.

4. Source code changes needed in the Android App to support NSD (Network Service Discovery)

Notes:
 (1) Below is just how I decided to implement it, you may want to separate the NSD related code out of the Activity into its own class, but for me that wasn't a problem.
 (2) I probably need to do something related to NSD in the Application lifecycle events of onPause(), onResume(), onDestroy() and onTeardown(). I haven't done that here yet.


4.A. Add some members to the Activity

I added this to my MainActivity but my app is small and so that's all I needed to do.

// Network Service Discovery related members
// This allows the app to discover the garagedoor.local
// "service" on the local network.
// Reference: http://developer.android.com/training/connect-devices-wirelessly/nsd.html
private NsdManager mNsdManager;
private NsdManager.DiscoveryListener mDiscoveryListener;
private NsdManager.ResolveListener mResolveListener;
private NsdServiceInfo mServiceInfo;
public String mRPiAddress;

// The NSD service type that the RPi exposes.
private static final String SERVICE_TYPE = "_workstation._tcp.";

   
4.B. Add some init code to the bottom of the Activity's onCreate() method.

mRPiAddress = "";
mNsdManager = (NsdManager)(getApplicationContext().getSystemService(Context.NSD_SERVICE));

initializeResolveListener();
initializeDiscoveryListener();
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

   
4.C. Add the following two new methods to your Activity.

 private void initializeDiscoveryListener() {

     // Instantiate a new DiscoveryListener
     mDiscoveryListener = new NsdManager.DiscoveryListener() {

         //  Called as soon as service discovery begins.
         @Override
         public void onDiscoveryStarted(String regType) {
         }

         @Override
         public void onServiceFound(NsdServiceInfo service) {
             // A service was found!  Do something with it.
             String name = service.getServiceName();
             String type = service.getServiceType();
             Log.d("NSD", "Service Name=" + name);
             Log.d("NSD", "Service Type=" + type);
             if (type.equals(SERVICE_TYPE) && name.contains("garagedoor")) {
                 Log.d("NSD", "Service Found @ '" + name + "'");
                 mNsdManager.resolveService(service, mResolveListener);
             }
         }

         @Override
         public void onServiceLost(NsdServiceInfo service) {
             // When the network service is no longer available.
             // Internal bookkeeping code goes here.
         }

         @Override
         public void onDiscoveryStopped(String serviceType) {
         }

         @Override
         public void onStartDiscoveryFailed(String serviceType, int errorCode) {
             mNsdManager.stopServiceDiscovery(this);
         }

         @Override
         public void onStopDiscoveryFailed(String serviceType, int errorCode) {
             mNsdManager.stopServiceDiscovery(this);
         }
     };
 }

 private void initializeResolveListener() {
     mResolveListener = new NsdManager.ResolveListener() {

         @Override
         public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
             // Called when the resolve fails.  Use the error code to debug.
             Log.e("NSD", "Resolve failed" + errorCode);
         }

         @Override
         public void onServiceResolved(NsdServiceInfo serviceInfo) {
             mServiceInfo = serviceInfo;

             // Port is being returned as 9. Not needed.
             //int port = mServiceInfo.getPort();

             InetAddress host = mServiceInfo.getHost();
             String address = host.getHostAddress();
             Log.d("NSD", "Resolved address = " + address);
             mRPiAddress = address;
         }
     };
 }


4.D. Make use of the mRPiAddress member where previously you used a hardcoded IP address.

Check that its not empty before using it. If its empty, it means the RPi's name couldn't be resolved.


Follow @dodgy_coder

Subscribe to posts via RSS