Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deep into the IoT trenches: how to build a connected product

Deep into the IoT trenches: how to build a connected product

Slides from my talk at Devoxx UK, Droidcon UK 2017
Video: https://www.youtube.com/watch?v=rZ4b7UxE920

Don't you love making your app robust against network failures? Now imagine your app talking to a Wi-Fi connected product... Locally when your home or via a server when you’re not. You'll certainly love handling those network switches!... And what if your app doesn't just control one product, but multiple ones? Some locally, some remotely connected?... Or how about having multiple users controlling the same product at the same time?... It goes without question that building apps for connected products is incredibly challenging.

From device discovery, over minimizing network requests to out of the box setup, this talk will cover it all. Packed with code examples, architecture suggestions and tons of best practices, you'll learn five years’ worth of hard-earned experience. This won't just kick start your IoT app development skills, but it'll also teach you how to keep your app scalable and maintainable towards the future.

Starting as early as 2011, Jeroen has built an impressive range of connected products: BT LE coffee machines, Wi-Fi speakers, connected air purifiers and even full blown connectivity libraries. Currently he is the lead Android developer for the official Philips Hue app, the biggest IoT system in the world.

Jeroen Mols

May 12, 2017
Tweet

More Decks by Jeroen Mols

Other Decks in Technology

Transcript

  1. @MOLSJEROEN CHALLENGES 1. Easy to use (and test) ▸ Wi-Fi

    setup ▸ Prepare for bad weather 2. Performant ▸ Fast discovery ▸ Maximise control speed 3. Scalable ▸ Lessons learned
  2. @MOLSJEROEN CHALLENGES 1. Easy to use (and test) ▸ Wi-Fi

    setup ▸ Prepare for bad weather 2. Performant ▸ Fast discovery ▸ Maximise control speed 3. Scalable ▸ Lessons learned
  3. @MOLSJEROEN WI-FI SETUP - CHALLENGES ▸ Typing Wi-Fi passwords ▸

    Network switching is fragile ▸ phone/tablet refuses to connect ▸ OS routes request over 4G ▸ Extremely slow feedback ▸ Users have to leave app on iOS ▸ Many bad weather scenarios
  4. @MOLSJEROEN ANDROID 5.0+ ▸ Behaviour change in Android 5.0 ▸

    Wi-Fi without internet ▸ Before: all requests sent over Wi-Fi ▸ After: all requests routed over 3G/4G ▸ Done for all, no subnet matching
  5. @MOLSJEROEN ANDROID 5.0+ - SINGLE REQUEST OVER WI-FI @RequiresApi(api =

    Build.VERSION_CODES.LOLLIPOP) public void singleRequestOverWifi(Context context) { NetworkRequest.Builder request = new NetworkRequest.Builder(); request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); ConnectivityManager connMan = getConnectivityManager(); connMan.registerNetworkCallback(request.build(), new NetworkCallback(){ @Override public void onAvailable(Network network) { // Do network request } }); }
  6. @MOLSJEROEN ANDROID 5.0+ - SINGLE REQUEST OVER WI-FI @RequiresApi(api =

    Build.VERSION_CODES.LOLLIPOP) public void singleRequestOverWifi(Context context) { NetworkRequest.Builder request = new NetworkRequest.Builder(); request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); ConnectivityManager connMan = getConnectivityManager(); connMan.registerNetworkCallback(request.build(), new NetworkCallback(){ @Override public void onAvailable(Network network) { // Do network request } }); }
  7. @MOLSJEROEN ANDROID 5.0+ - SINGLE REQUEST OVER WI-FI @RequiresApi(api =

    Build.VERSION_CODES.LOLLIPOP) public void singleRequestOverWifi(Context context) { NetworkRequest.Builder request = new NetworkRequest.Builder(); request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); ConnectivityManager connMan = getConnectivityManager(); connMan.registerNetworkCallback(request.build(), new NetworkCallback(){ @Override public void onAvailable(Network network) { // Do network request } }); }
  8. @MOLSJEROEN ANDROID 5.0+ - SINGLE REQUEST OVER WI-FI @RequiresApi(api =

    Build.VERSION_CODES.LOLLIPOP) public void singleRequestOverWifi(Context context) { NetworkRequest.Builder request = new NetworkRequest.Builder(); request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); ConnectivityManager connMan = getConnectivityManager(); connMan.registerNetworkCallback(request.build(), new NetworkCallback(){ @Override public void onAvailable(Network network) { // Do network request } }); }
  9. @MOLSJEROEN ANDROID 5.0+ - ALL REQUESTS OVER WI-FI @RequiresApi(api =

    Build.VERSION_CODES.LOLLIPOP) public void allRequestsOverWifi(Context context) { NetworkRequest.Builder request = new NetworkRequest.Builder(); request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); final ConnectivityManager connMan = getConnectivityManager(); connMan.registerNetworkCallback(request.build(), new NetworkCallback() { @Override public void onAvailable(Network network) { connMan.bindProcessToNetwork(network); // Do network requests } }); }
  10. @MOLSJEROEN ANDROID 5.0+ - ALL REQUESTS OVER WI-FI @RequiresApi(api =

    Build.VERSION_CODES.LOLLIPOP) public void allRequestsOverWifi(Context context) { NetworkRequest.Builder request = new NetworkRequest.Builder(); request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); final ConnectivityManager connMan = getConnectivityManager(); connMan.registerNetworkCallback(request.build(), new NetworkCallback() { @Override public void onAvailable(Network network) { connMan.bindProcessToNetwork(network); // Do network requests } }); }
  11. @MOLSJEROEN ALTERNATIVES ▸ One product as ethernet bridge ▸ WPS

    ▸ Other mechanisms for communication ▸ e.g High frequency sound, … ▸ Still need to enter Wi-Fi password ▸ WAC (Homekit devices) ▸ …
  12. @MOLSJEROEN INTERESTING SCENARIOS ▸ Bridge (almost) full ▸ Network request

    failed for reason X ▸ Resource doesn’t exist ▸ Connection lost ▸ Authentication lost ▸ Dynamic state changes ▸ …. => nearly impossible to reproduce manually
  13. @MOLSJEROEN SIMULATOR COMMUNICATIONSTRATEGY REMOTESTRATEGY LOCALSTRATEGY SIMULATORSTRATEGY PRODUCT 1.. ▸ Hardcoded

    responses ▸ Simulate dynamic behaviour ▸ Recreate bad weather scenarios
  14. @MOLSJEROEN SIMULATOR COMMUNICATIONSTRATEGY REMOTESTRATEGY LOCALSTRATEGY SIMULATORSTRATEGY PRODUCT 1.. ▸ Hardcoded

    responses ▸ Simulate dynamic behaviour ▸ Recreate bad weather scenarios
  15. @MOLSJEROEN CHALLENGES 1. Easy to use (and test) ▸ Wi-Fi

    setup ▸ Prepare for bad weather 2. Performant ▸ Fast discovery ▸ Maximise control speed 3. Scalable ▸ Lessons learned
  16. @MOLSJEROEN LOCAL DISCOVERY ▸ Multicast based ▸ Often Unicast responses

    ▸ SSDP, mDNS,… ▸ Alternatives ▸ IP scan ▸ Manual IP ▸ Proprietary product IP server
  17. @MOLSJEROEN LOCAL DISCOVERY - IP SERVER https://www.meethue.com/api/nupnp [ { "id":"001788fffe100491",

    “internalipaddress":"192.168.2.23" }, { "id":"001788fffe09a168", “internalipaddress":"192.168.88.252" } ]
  18. @MOLSJEROEN REMOTE DISCOVERY ▸ Web service ▸ product announces ▸

    interface to get products ▸ Security ▸ Local setup first required
  19. @MOLSJEROEN OPTIMISING DISCOVERY 1. Discover local and remote 2. Connect

    local and remote ▸ ~5 - 10 seconds ▸ Empty UI when no products found
  20. @MOLSJEROEN OPTIMISING DISCOVERY - REMEMBER PRODUCTS 1. Products from database

    2. Discover local and remote 3. Connect local and remote ▸ ~5 - 10 seconds ▸ Disconnected device when not found ▸ Need for deleting devices
  21. @MOLSJEROEN OPTIMISING DISCOVERY - REMEMBER STATE 1. Products & last

    state from database 2. Discover local and remote 3. Connect local and remote 4. Update UI with actual state ▸ ~3 - 4 seconds ▸ UI instantly ready ▸ Control possible after discovery
  22. @MOLSJEROEN OPTIMISING DISCOVERY - REMEMBER CONNECTION INFO 1. Products, last

    state & connection info from database 2. Attempt local and remote connection 3. Fallback discovery 4. Update UI with actual state ▸ ~0 seconds ▸ UI & Control instantly ready ▸ Remember IP address and last SSID
  23. @MOLSJEROEN #PROTIP - SIMPLIFY DEVELOPMENT BY LIMITING STATE TRANSITIONS ▸

    Transition local to remote connection always via disconnected
  24. @MOLSJEROEN CONTROL SPEED 1. Always prefer local connection 2. Optimise

    local connection 3. Reduce amount of requests 4. Instant UI ▸ Implicit cost savings ▸ Remote connection usually not critical
  25. @MOLSJEROEN 1. PREFER LOCAL CONNECTION COMPOSITESTRATEGY PRODUCT LOCALSTRATEGY SIMULATORSTRATEGY REMOTESTRATEGY

    COMMUNICATIONSTRATEGY - boolean isAvailable() ▸ Inherently faster ▸ Cost benefit ▸ Force connection
  26. @MOLSJEROEN 2. OPTIMISE LOCAL CONNECTION ▸ Data compression ▸ Cache

    headers ▸ Socket reuse ▸ => OKHttp ▸ Interference UDP/TCP or other traffic ▸ Wireshark low level traffic
  27. @MOLSJEROEN 2. OPTIMISE LOCAL CONNECTION ▸ Data compression ▸ Cache

    headers ▸ Socket reuse ▸ => OKHttp ▸ Interference UDP/TCP or other traffic ▸ Wireshark low level traffic
  28. @MOLSJEROEN 3. REDUCE # REQUESTS private Map<String, Object> pendingAttr =

    new HashMap<>(); public void setOn(boolean isOn) { synchronized (pendingAttr) { pendingAttr("KEY_ON", isOn); } sendUpdate(); } private void sendUpdate() { strategy.doRequest(updateRequest); }
  29. @MOLSJEROEN 3. REDUCE # REQUESTS private Map<String, Object> pendingAttr =

    new HashMap<>(); public void setOn(boolean isOn) { synchronized (pendingAttr) { pendingAttr("KEY_ON", isOn); } sendUpdate(); } private void sendUpdate() { strategy.doRequest(updateRequest); }
  30. @MOLSJEROEN 3. REDUCE # REQUESTS private Map<String, Object> pendingAttr =

    new HashMap<>(); public void setOn(boolean isOn) { synchronized (pendingAttr) { pendingAttr("KEY_ON", isOn); } sendUpdate(); } private void sendUpdate() { strategy.doRequest(updateRequest); }
  31. @MOLSJEROEN 3. REDUCE # REQUESTS private Map<String, Object> pendingAttr =

    new HashMap<>(); public void setOn(boolean isOn) { synchronized (pendingAttr) { pendingAttr("KEY_ON", isOn); } sendUpdate(); } private void sendUpdate() { strategy.doRequest(updateRequest); }
  32. @MOLSJEROEN 3. REDUCE # REQUESTS private UpdateRequest updateRequest = new

    UpdateRequest() { @Override public String getEndpoint() { return "/lights"; } @Override public Map<String, Object> getAttributes() { synchronized (pendingAttr) { Map<String, Object> copy = new HashMap<>(pendingAttr); pendingAttr.clear(); return copy; } }};
  33. @MOLSJEROEN 3. REDUCE # REQUESTS private UpdateRequest updateRequest = new

    UpdateRequest() { @Override public String getEndpoint() { return "/lights"; } @Override public Map<String, Object> getAttributes() { synchronized (pendingAttr) { Map<String, Object> copy = new HashMap<>(pendingAttr); pendingAttr.clear(); return copy; } }};
  34. @MOLSJEROEN 3. REDUCE # REQUESTS private UpdateRequest updateRequest = new

    UpdateRequest() { @Override public String getEndpoint() { return "/lights"; } @Override public Map<String, Object> getAttributes() { synchronized (pendingAttr) { Map<String, Object> copy = new HashMap<>(pendingAttr); pendingAttr.clear(); return copy; } }};
  35. @MOLSJEROEN 3. REDUCE # REQUESTS private class CommunicationStrategy { private

    Set<UpdateRequest> queue = new LinkedHashSet<>(); public void doRequest(UpdateRequest updateRequest) { queue.add(updateRequest); // logic to trigger requests } }
  36. @MOLSJEROEN 3. REDUCE # REQUESTS private class CommunicationStrategy { private

    Set<UpdateRequest> queue = new LinkedHashSet<>(); public void doRequest(UpdateRequest updateRequest) { queue.add(updateRequest); // logic to trigger requests } }
  37. @MOLSJEROEN 3. REDUCE # REQUESTS private class CommunicationStrategy { private

    Set<UpdateRequest> queue = new LinkedHashSet<>(); public void doRequest(UpdateRequest updateRequest) { queue.add(updateRequest); // logic to trigger requests } }
  38. @MOLSJEROEN CHALLENGES 1. Easy to use (and test) ▸ Wi-Fi

    setup ▸ Prepare for bad weather 2. Performant ▸ Fast discovery ▸ Maximise control speed 3. Scalable ▸ Lessons learned
  39. @MOLSJEROEN DEVELOPING A SYSTEM 0 0.5 1 1.5 2 2.5

    3 3.5 4 4.5 1 2 3 4 5 6 7 8 9 10 11 12 New Features Maintenance
  40. @MOLSJEROEN DEVELOPING A SYSTEM 0 0.5 1 1.5 2 2.5

    3 3.5 4 4.5 1 2 3 4 5 6 7 8 9 10 11 12 New Features Maintenance
  41. @MOLSJEROEN DEVELOPING A SYSTEM 0 0.5 1 1.5 2 2.5

    3 3.5 4 4.5 1 2 3 4 5 6 7 8 9 10 11 12 New Features Maintenance
  42. @MOLSJEROEN DEVELOPING A SYSTEM 0 0.5 1 1.5 2 2.5

    3 3.5 4 4.5 1 2 3 4 5 6 7 8 9 10 11 12 New Features Maintenance
  43. @MOLSJEROEN DEVELOPING A SYSTEM 0 0.5 1 1.5 2 2.5

    3 3.5 4 4.5 1 2 3 4 5 6 7 8 9 10 11 12 New Features Maintenance
  44. @MOLSJEROEN DEVELOPING A SYSTEM 0 0.5 1 1.5 2 2.5

    3 3.5 4 4.5 1 2 3 4 5 6 7 8 9 10 11 12 New Features Maintenance
  45. @MOLSJEROEN MAINTENANCE ▸ Big focus on automated testing ▸ Need

    for good architecture ▸ Over-engineering has a huge cost ▸ Avoid Hype driven development ▸ Think well before introducing new features
  46. @MOLSJEROEN #FAILUREHALLOFFAME ▸ Detect working internet connection (Wifi, no internet)

    ▸ Instrumentation tests ▸ Lock orientation to portrait ▸ Nested fragments ▸ Broadcasts as callbacks ▸ Post( new Runnable(…) ) ▸ Null checks ▸ …. RELATED BLOGPOSTS
  47. @MOLSJEROEN CONCLUSION ▸ Developing connected products is hard ▸ Developing

    systems is even harder ▸ Maximise local connections ▸ Keep things simple for users ▸ Strong focus on automated testing ▸ Proper architecture
  48. @MOLSJEROEN IMAGE CREDITS • Welcome image http://www.store.meethue.com/media/catalog/product/cache/1/ feature_img_7/9df78eab33525d08d6e5fb8d27136e95/e/x/expand_your_ecosystem_1.jpg • Wi-fi

    setup https://images.philips.com/is/image/PhilipsConsumer/AW3000_10-MI1-global-001? $jpglarge$&wid=1250 • Good vs bad weather https://images.philips.com/is/image/PhilipsConsumer/FC8830_82-U1P-global-001? $jpglarge$&wid=1250 • Discovery https://s.blogcdn.com/slideshows/images/slides/403/398/9/S4033989/slug/l/philips-hue-motion- sensor-wall-attached-1.jpg • Control speed http://www.philips.com/consumerfiles/newscenter/main/shared/assets/de/Downloadablefile/ press/elektro_hausgeraete/20140716_Philips_Air_AC4072_11_Lifestyle_4.jpg • Developing a system http://www.philips.com/consumerfiles/newscenter/main/standard/resources/corporate/ press/2014/IFA2014/Saeco-GranBaristo-Avanti_image-5.jpg • Warp up http://www.philips.com/consumerfiles/newscenter/main/standard/resources/corporate/press/2014/ IFA2014/Hue-Beyond-lifestyle_all-three.jpg