name: inverse class: center, middle, inverse layout: true .header[.floatleft[.teal[Christopher Biggs] — Elm for IoT].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[elmbne] Nov 2018]] --- name: callout class: center, middle, italic, bulletul layout: true .header[.floatleft[.teal[Christopher Biggs] — Elm for IoT].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[elmbne] Nov 2018]] --- layout: true template: callout .header[.floatleft[.teal[Christopher Biggs] — Elm for IoT].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[elmbne] Nov 2018]] --- class: center, middle template: inverse # Elm for The Internet of Things ## .green[Quick and Beautiful user interfaces for everything] .bottom.right[ Christopher Biggs, .logo[Accelerando Consulting]
@unixbigot .logo[@accelerando_au] ] ??? G'day folks, I'm Christopher Biggs. --- # Who am I? ## Christopher Biggs — .teal[@unixbigot] — .logo[@accelerando_au] * Brisbane, Australia * Founder, .logo[Accelerando Consulting] * 20+ years in IT as developer, architect, manager * Accelerando is a "full service" consultancy - chips to cloud * ***IoT, DevOps, Big Data*** ??? I've been involved with electronic gadgets since I was a teenager, and throughout 20 years as a software engineer, architect and manager. I operate Accelerando Consulting which helps businesses use technology to reduce stress. Really, that's what I believe, that technology is the gradual process of freeing humanity from drudgery and fear, and I started Accelerando because I wanted to contribute to a future that I will be excited to live in. --- layout: true template: callout .crumb[ # Welcome # What ] --- class: center, middle template: invert # What is The Internet of Things, anyway? --- # Computers for humans, but not *for* humans --- # Set(up) and forget --- # (Relatively) low-powered devices ## "Only" about 30000 Moonshots each * CPU: 100-1000MHz per core (1-4 cores) -- * Memory: 64-1000MB RAM -- * Storage: 1-16GB of Flash ROM -- * UI: would struggle to run a screen bigger than 320x240 -- * luckily, we all have a .red[supercomputer] in our pockets (2 MILLION moonshots) --- layout: true template: callout .crumb[ # Welcome # What # Why ] --- class: center, middle template: inverse # UX for IoT --- # Well, not this .fig40l[ ] .fig40r[ ] --- .fig80[ ] .spacedown[ # Not this either ] --- .fig60[ ] .spacedown[ # How about this? ] --- .fig50l[  ] .fig40r[  ] --- layout: true template: callout .crumb[ # Welcome # What # Why # How ] --- class: center, middle template: inverse # A minimal but fully responsive Elm UI --- # Skeletons considered harmful .code[ ```elm type alias Model = { mdc : Material.Model Msg , key : Browser.Navigation.Key , url : Demo.Url.Url , buttons : Demo.Buttons.Model Msg , cards : Demo.Cards.Model Msg , checkbox : Demo.Checkbox.Model Msg , chips : Demo.Chips.Model Msg ... } ``` ] .code[ ```elm type Msg = Mdc (Material.Msg Msg) | NoOp | UrlChanged Url.Url | UrlRequested Browser.UrlRequest | Navigate Demo.Url.Url | ButtonsMsg (Demo.Buttons.Msg Msg) | CardsMsg (Demo.Cards.Msg Msg) ```] --- # Skeletons considered harmful .code[ ```elm type alias Model m = { mdc : Material.Model m , rtl : Bool } type Msg m = Mdc (Material.Msg m) | ToggleRtl update : (Msg m -> m) -> Msg m -> Model m -> ( Model m, Cmd m ) update lift msg model = case msg of Mdc msg_ -> Material.update (lift << Mdc) msg_ model ToggleRtl -> ( { model | rtl = not model.rtl }, Cmd.none ) ```] --- # Skeletons considered harmful ## So here's *my** skeleton .code[ ```elm type Msg = Mdc (Material.Msg Msg) | ShowDrawer Bool | EditConfig | EditConfigInput String String | EditConfigSave Bool | GetConfig | GotConfig Result String view : Model -> Html Msg view model = Html.div [] [ header model , drawer model , page model ] ```] .footnote[https://github.com/unixbigot/esp-skeleton] --- # I have everything just the way I like it * Around 1000 lines of elm * 3 API Calls * JS, 8 fonts, some CSS, total abouat 500k --- # I have everything just the way I like it ## ...then they deprecated the framework .fig100[ ] --- # Take two, elm-mdc .fig100[ ] --- .fig80[ ] .spacedown[ # Take two, elm-mdc] --- # The bad news .fig100[ ] --- # Elm-MDC as a submodule .code[ ```gmake # Makefile excerpt: ELMOBJ=static/js/elm.js elm: elm-mdc $(ELMOBJ) elm-mdc: elm-mdc/README.md elm-mdc/build/elm-mdc.js elm-mdc/build/elm-mdc.js: make -C elm-mdc elm-mdc/README.md: git submodule update elm-dev: main.elm elm-live --static ./static $< -- --debug --output $(ELMOBJ) $(ELMOBJ): main.elm elm make $(ELMFLAGS) $< --output $@ ```] --- # Elm-MDC as a submodule .code[ ```json { "type": "application", "source-directories": [ ".", "./elm-mdc/src" ], "elm-version": "0.19.0", ,... } ```] --- # Other bad news ## No maps for you --- # What do you mean fonts.google.com not found? .code[ ```html
WaterWise
```] .code[ ```css /* latin */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 500; src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } ```] --- # What do you mean fonts.google.com not found? .code[ ```html
WaterWise
```] .code[ ```css @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; src: local('Roboto Light'), local('Roboto-Light'), url(Roboto-Light.ttf) format('truetype'); } ```] --- # More good news ## There's an API for that * C++ is not super friendly for writing APIs -- * but it's improving .code[ ```c++ server.on("/api/version", HTTP_GET, [](AsyncWebServerRequest * request) { char buf[120]; snprintf(buf, sizeof(buf), "{\"buildDate\":\"%s\", \"buildTime\":\"%s\", \"buildRef\":\"%s\"}", __DATE__, __TIME__, __VERSION__); request->send(200,"text/json", buf); }); ```] --- # 90% of the time you can forget about APIs * `npm install elm-live` * `npm install json-server` .code[ ```json { "sensors": [ { "id": 1, "name": "Battery", "value": 3.99 }, { "id": 2, "name": "Temperature", "value": 42.0 }, { "id": 3, "name": "DiskFree", "value": 1024000 }, { "id": 4, "name": "RamFree", "value": 512000 } ], "actuators": [ { "id": 5, "name": "Reboot" }, { "id": 6, "name": "LED" } ] } ```] --- # Containerise your build .code[ ```Dockerfile FROM node:8 RUN mkdir -p /app/static WORKDIR /app ADD package.json /app/ RUN npm install ADD elm-mdc /app/elm-mdc/ ADD Makefile main.elm elm.json db.json go.sh /app/ ADD static/ /app/static/ ENV PATH /app/node_modules/.bin:${PATH} RUN make CMD ["/app/go.sh"] ```] --- # A Word about webservers * The built-in ESP webserver is fairly pants * Use `https://github.com/me-no-dev/ESPAsyncWebServer` * Maybe Use `https://github.com/tzapu/WiFiManager` * Definitely Use [Arduino-OTA](http://esp8266.github.io/Arduino/versions/2.0.0/doc/ota_updates/ota_updates.html) * All of these lined up: [https://github.com/unixbigot/esp-skeleton](https://github.com/unixbigot/esp-skeleton) --- # Webpack, I suppose I'll have to learn it one day -- ## ...but not today .code[ ```gmake upload: putelm putstatic compare: for f in $(ELMOBJ) $(STATICFILES) ; do ./compare.sh $${f}.gz ; done putelm: $(ELMOBJ) gzip -9v <$(ELMOBJ) >$(ELMOBJ).gz upload.sh $(ELMOBJ).gz putstatic: for s in $(STATICFILES) ; \ do \ f=`basename $${s}`; \ gzip -9v
static/$${f}.gz ;\ upload.sh static/$${f}.gz ;\ sleep 2 ;\ done ```] --- # Simple uploads with verification .code[ ```bash #!/bin/bash # upload.sh [ -n "$FILE" ] || FILE=$1 [ -n "$DEVICE" ] || DEVICE=device.local echo "Uploading ${FILE} to ${DEVICE}" curl ${VARG} -F "data=@${FILE}" http://${DEVICE}/api/upload ```] .code[ ```bash #!/bin/bash # compare.sh [ -n "$FILE" ] || FILE=$1 [ -n "$DEVICE" ] || DEVICE=device.local SUM=`shasum "${FILE}" | awk '{print $1}'` curl -s -o .compare "http://${DEVICE}/`basename ${FILE}`" DLSUM=`shasum .compare | awk '{print $1}'` if [ "${DLSUM}" != "${SUM}" ] then echo " MISMATCH. ${DLSUM} != ${SUM}" exit 1 fi ```] --- # Let go of your serial port ## You have nothing to lose but your parity bits .code[ ```C++ #define __DEBUG__(l,...) {if(debuglevel>=l) { \ Serial.printf(__VA_ARGS__); Serial.println();\ SYSLOG(l,__VA_ARGS__) \ }} #define ALERT( ...) __DEBUG__(L_ALERT ,__VA_ARGS__) #define NOTICE(...) __DEBUG__(L_NOTICE,__VA_ARGS__) #define INFO(...) __DEBUG__(L_INFO ,__VA_ARGS__) #define SYSLOG(l,...) \ char syslogbuf[512]; \ int offset = 0; \ snprintf(syslogbuf+offset, sizeof(syslogbuf)-offset, "<%d>", (facility<<3)+severity); \ ... snprintf(syslogbuf+offset, sizeof(syslogbuf)-offset, __VA_ARGS__); \ _udpsend(SYSLOG_host, SYSLOG_port, syslogbuf, strlen(syslogbuf)); void _udpsend(IPAddress dst, unsigned int port, char *buf, unsigned int len) { UDP.beginPacket(dst, port); UDP.write(buf, len); UDP.endPacket(); } ```] --- # A dishwasher is not just for Christmas ## (A little more on Over-the-Air updates) .code[ ```bash go get -u github.com/arduino/arduino-cli arduino-cli core install esp8266:esp8266 arduino-cli compile -b esp8266:esp8266:generic -o esp-skeleton.bin esp-skeleton.ino espota.py -i 192.168.65.173 -p 3232 --auth= -f esp-skeleton.bin sudo tcpdump -i en0 udp port 514 & #include
```] --- # Something Something Cloud * "Cloud IoT Frameworks" (eg Amazon, Google) are mostly MQTT-with-benefits * Take a look at [mongoose-os.com](mongoose-os.com) * Doing MQTT from Elm: .code[ ```elm type alias MqttMessage = (String, String) port mqttReceive : (MqttMessage -> msg) -> Sub msg subscriptions model = mqttReceive GotMqttMessage update msg model = case msg of GotMqttMessage e -> let topic = Tuple.first e payload = Tuple.second e _ = Debug.log <| "Received MQTT at "++topic++" : "++payload events_ = e :: model.mqttEvents in ( {model | mqttEvents = events_ } , Cmd.none ) ```] --- # Something Something Cloud * Javascript MQTT/Websocket client as an Elm Port: .code[ ```html ```] ...whoops .code[ ```html ```] ...later .code[ ```javascript var mqtt_client = new Paho.MQTT.Client("wss://mqtt.local:8833", '','', "elm") mqtt_client.onMessageArrived = onMqttMessageArrived; mqtt_client.connect({reconnect:true}); function onMqttMessageArrived(message) { console.log("onMessageArrived:"+JSON.stringify(message)); app.ports.mqttReceive.send([message.payloadString, message.payloadString]) } ```] --- layout: true template: callout .crumb[ # Welcome # What # Why # How # Wrapup ] --- .fig30[  ] # Recap .nolm[ * IoT, aka "headless computers", still needs UI * Sturgeon's Law applies: 90% of current IoT UX is Cr*p * What does good IoT UX even look like? * A starter-skeleton for an Elm Single-page-app * API's and Servers and Bugs, Oh My * Devops for Dishwashers ] --- # Resources, Questions ## Related talks - [http://christopher.biggs.id.au/#talks](http://christopher.biggs.id.au/#talks) ## Me - Christopher Biggs - Twitter: .blue[@accelerando_au] - Email: .blue[christopher@biggs.id.au] - Slides, and getting my advice: http://christopher.biggs.id.au/ - Accelerando Consulting - IoT, DevOps, Big Data - https://accelerando.com.au/ ??? Thanks for your time today, I'm happy to take questions in the few moments remaining and I'm here all week if you want to have a longer chat. Over to you. --- # Links * Code from this talk - [github.com/unixbigot/esp-skeleton](https://github.com/unixbigot/esp-skeleton) * Elm - [elm-lang.org](http://elm-lang.org/) * Adventures in Elm by @jessitron - [Slides](http://yowconference.com.au/slides/yow2016/Kerr-AdventuresElm.pdf) - [Video](https://www.youtube.com/watch?v=xrmeU1JNt7s) * More [me talking about elm](http://christopher.biggs.id.au/talk/2018-05-23-functional-frontends-with-elm/) * Elm-MDC - [aforemny.github.io/elm-mdc](https://aforemny.github.io/elm-mdc/#) * Elm-MDL - A ~~beautiful and simple~~ .red[deprecated] UI framework - [demo](https://debois.github.io/elm-mdl/) - [doco](http://package.elm-lang.org/packages/debois/elm-mdl/latest)