Arduino Development
Oracle's Code Card and playing with Arduino on BastionLinux
The crack
Oracle are doing these CodeCard devices at their trade shows at the moment. These are actually quite a nifty piece of kit: an ESP-12F (not quite the gruntier ESP32) married to an e-paper display with a couple of buttons to trigger events (short + long holds on A, B buttons - 4 combos) that will shuffle a slideshow of business-card like templates across this display by going out to the interwebs for some basic JSON and then rendering it on the chip.
A bit of a Hack
Since it's all a bit of a wheeze, one of our Australian-local Oracle colleagues has released the code here. I've not really spent much time on Arduino ecosystem, so this seemed like an opportunity to purview the consumer face of IOT (Internet Of Tat). There are some awesome cross-compiler tools for AVR-based chips and it should be quite possible to utilise a classical C/Makefile environment to progress, but for my first foray, I've gone with the Arduino IDE (which ironically since we're discussing Oracle is a Java application). There is a great cheat sheet which will get you up and running; since I on occasion hang at OzBerryPi, I got quickly up to speed with this with some friendly advice at their last event - note to noobs; you have to restart Arduino IDE each time you download a plugin (seems Java reflection hasn't arrived yet - or perhaps Windoz developers maintain upstream Arduino IDE).
There are two facets to the delivered application; the firmware which makes WiFi calls to endpoints to receive and render the JSON; and a configurator which allows you to save the WiFi ESSID and these endpoint URLs. To reflash the firmware, you need the AruinoIDE, and the application source code. To simply save the wifi info, there is a configuration script. The one in the codebase is a little esoteric; and really only designed for Oracle staff to flash devices at exhibition spaces. Have a crack at these here:
~/.codecardrc
# WiFi setup - you may need to hotspot via your phone if out and about ... essid = XXX secret = xxx [a-short] url = http.... # can set url 'method' (dunno why - it's probably always GET # can set url 'fingerprint' the TLS fingerprint for host validation # fingerprint = # method = [a-long] url = http... [b-short] url = http... [b-long] url = http...
codecard-configurator.py (chmod ug+x)
#!/usr/bin/python3 # initialise modules.. import binascii, os, serial, sys, time, warnings import serial.tools.list_ports from art import * from configobj import ConfigObj from optparse import OptionParser def command(tty, cmd, bufsize=2048, wait=1): """ send a command to codecard """ tty.write(b'%s\r\n' % cmd.encode()) # write bytes ... time.sleep(wait) print(tty.read(bufsize)) def read(tty, buf='', chunksize=200): """ consume characters from serial tty """ exit = 0 while exit < 2: more = tty.read(chunksize) buf += more exit = len(more) == 0 and exit + 1 or 0 return buf HELP = ''' ====================================================== PURPOSE: Code Card Configurator will update your Code Card configuration settings and perform button 'A' shortpress test.. DIRECTIONS: 1. Plug-in your Code Card 2. Turn on the Code Card (i.e. via hardware power switch) 3. Press 'Enter' to start the process 4. On your Code Card, perform simultaneous button 'A+B' shortpress to boot the Code Card into Configuration Mode Your card will be automatically configured - progress will be logged to stdout WHEN READY: Ensure steps 1&2 are complete, then.. Press Enter to continue... FLASHING CODECARD INSTRUCTIONS: 1. Plug-in your Code Card 2. Set up tty (Arduino IDE [Tools]/[Port]) /dev/ttyUSB0 3. Open tty (Arduino IDE [Tools]/[Monitor]) 4. Turn on the Code Card (i.e. via hardware power switch) 5. Press and hold button A until 5 seconds after the following output is displayed: Shuting down... Shuting down... 6. Flash the device (Arduino IDE [Sketch]/[Upload]) ''' parser = OptionParser(usage=HELP) parser.add_option('-c', '--config', default='~/.codecardrc', help="Configuration inifile (~/.codecardrc)") (options, args) = parser.parse_args() config = ConfigObj(os.path.expanduser(options.config)) # menu/header.. print ("\r\n") strTitle=text2art("CodeCard") print(strTitle, "Oracle Code Card Configurator...\n") #print(HELP) input(" Press Enter to configure the CodeCard...") # list all serial ports.. print ("\r\nENUMERATING PORTS:\r\n") for p in serial.tools.list_ports.comports(): print (" Port Details:") print (" Device: " , p.device) print (" Description: " , p.description) # identify our code card by the `CP210x` string in the port description.. # win: "Silicon Labs CP210x USB to UART Bridge" # mac: "CP2104 USB to UART Bridge Controller" codecard_port = [ p.device for p in serial.tools.list_ports.comports() if 'Silicon Labs CP210x USB to UART Bridge' in p.description or 'CP2104 USB to UART Bridge Controller' in p.description ] # exceptions.. if not codecard_port: raise IOError("No Code Card found !!") if len(codecard_port) > 1: warnings.warn('Multiple Code Cards found !! - Using the first one..') # code card port details.. print ("\r\nCODE CARD FOUND !!") time.sleep(1.5) print ("CODE CARD PORT DETAIL:") print (" " , codecard_port) ser = serial.Serial(codecard_port[0]) print (" " , ser) ser.close() # configure the serial connection to our code card.. ser = serial.Serial( port=codecard_port[0], baudrate=115200, timeout=5, ) s = ser.read(1488) # convert our buffer content to string.. s = str(read(ser, s))[2:-1] # parse buffer content for confirmation that we're in config mode.. if s.find('developer.oracle.com/codecard') != -1: print ("\r\nCODE CARD READY !!") time.sleep(3) print ("DEBUG - CONFIG MODE BUFFER:") for line in s.split('\r\n'): print(line) print ("\r\nCONFIGURING WIRELESS:") command(ser, "ssid=%s" % config['essid']) command(ser, "password=%s" % config['secret']) print ("\r\nCONFIGURING END POINTS:") for key in config.sections: button = "%s%i" % (key[0], key.endswith('short') and 1 or 2) command(ser, "button%s=%s" % (button, config[key]['url'])) for slot in ('fingerprint', 'method'): try: command(ser, "%s%s=%s" % (slot, button, config[key][slot])) except KeyError: pass #command(ser, "restart", 240, 5) #command(ser, "ls", 661) print ("\r\n") print ("SHORTPRESS BUTTON A..") print ("DEBUG - BUTTON PRESS BUFFER:") command(ser, "shortpressa", 500) print(read(ser, b'', 100)) else: print ("\r\n") print ("CODE CARD NOT READY IN CONFIG MODE") print ("QUITTING..")
The Web Service
Obviously, the core thing you're aiming for tinkering with the CodeCard, is what it receives back from it's WiFi calls in response to button presses. Oracle's solution/demo (and the delivered app) is something hosted on Oracle Cloud and they give some excellent documentation on how to spin up an instance, run docker, and deploy your app into a container. It all makes my head hurt! Instead you might like to try something like this I threw together using Bottle - the task isn't big enough to warrant the big brother - Flask.
codecard-server.py
#!/usr/bin/python3 from bottle import hook, route, run, request, response, static_file def img_url(fname): #return "%s/static/%s" % (request.url.endswith('/') and request.url[:-1] or request.url, fname) return 'http://10.1.1.22:8080/static/%s' % fname @hook('after_request') def application_json(): response.headers['Content-Type'] = 'application/json' @route('/static/') def server_static(filepath): return static_file(filepath, root='./static') @route('/') def default(): return { # 1-11 "template": "template11", "title": "Alan Milligan", "subtitle": "Chief Automation Officer", "bodytext": "This is the body", "icon": img_url("alan-grey-128.bmp"), # OPTIONAL ... # "backgroundColor": "[white|black]", # "badge": [0-100] It will override the icon "badge": "", # "backgroundImage": "[oracle|codeone | BMP url]" Only for templates that have backgrounds # "fingerprint": "" The SHA-1 signature of the server containing the custom icon or backgroundImage URL. } @route('/funny') def funny(): return { "template": "template1", "title": "Alan", "subtitle": "Resident hellraiser", "bodytext": "Making Jack Nicholson look like a schoolboy", "icon": img_url("alan-jester-64.bmp"), "badge": "" } @route('/rambling') def rambling(): return { "template": "template1", "title": "Alan Milligan", "subtitle": "Last Bastion Network", "bodytext": "BastionLinux, DevOps Solutions and Consulting, Continuous Integration and Delivery, Software Development, Amazon Web Services", "icon": img_url("alan-grey.bmp"), "badge": "" } @route('/brief') def brief(): return { "template": "template3", "title": "Alan Milligan", "subtitle": "Chief Automation Officer", "bodytext": "http://linux.last-bastion.net", "backgroundColor": "white", "icon": img_url("bastionlinux-icon.bmp"), "badge": "", } @route('/background') def background(): return { "template": "template8", "backgroundColor": "white", #"backgroundImage": "oracle", # codeone|oracle "backgroundImage": img_url("bastionlinux-bg.bmp"), # codeone|oracle } run(host='0.0.0.0', port=8080)
Application Firmware
So you've got this far and you're still with us! You've now reached the bit where the doddle is now completely over! You see, it turns out the actual flashed firmware is more buggy than an Amish conveyance - outside of what Oracle demonstrates at least. The actual software suite does rather reek of a university project rather than a demonstration of best in class design for this hardware platform (but we'll not dwell on this as it is a wheeze after all). But this does lead to exasperation when figuring out and correcting some quite consequent failures.
Silly of me I know; but I aggressively downloaded the latest dependency libraries. The main workhorse, ArduinoJSON has had a major points release from 5.x to 6.x. I had to upgrade the codebase to support this. To be fair, Oracle do state the 5.x in setting up your ArduinoIDE; but I abhor ossification in software projects.
Next, the entire HTTP client layer was completely broken. Oracle have hard-coded all their images into the firmware so that no web requests are necessary to render them; they thus don't see any of the image download/rendering problems. But not only this; the actual HTTP end-point requests are also incorrect. Alas for the ESP chipset, it completely does not help itself; the delivered ESP8266 HTTP library suite is laughably simplistic: it's nothing more than File/Stream IO methods; there is no additional layer for HTTP headers, payload; Cookies; Response objects etc - these are all left up to the application; and here is a classic example of this overwhelming the developer.
I had to make a number of critical changes to fix basic HTTP calls; and I had to fix the responses to accept both HTTP 1.0 and HTTP 1.1 replies - I think there is a strong lesson here that when dealing with these hobbyist chipsets, you should enter the fray with very low expectations of what the SDK's will have for you.
One last observation is that it would seem the e-paper display unit is actually natively expected to be mounted in portrait; all of your images need to be rotated 90 degrees to render properly. This has an interesting corollary in that I'm still baffled as to exactly which corner is 0,0. The third-party graphics rendering library (Adafruit GFX) is nothing compared to the heyday of Sun CDE and creates random black backgrounds behind images, places them many pixels out of place regardless of where you attempt to anchor them.
I would not describe myself as any expert, having only played for a day or so; but I do have the card able to be fully independently branded. I have a pull request here or you can just clone my fork such that you too can get at least as far as I have ...
To Conclude
One should never look a gift horse in the mouth; and this is actually a very expensive and capable piece of kit as a corporate giveaway. Oracle have provided the end-to-end set of tools to both exploit it and to readily interactively discover how you might bring a simple consumer electronic product to market. The software suite, while agricultural, provides you with all of the necessary insight to own whatever project you can think to do with the card. I suspect I'm going to keep one in my bag and shamelessly use on a lanyard doing the rounds at conferences - for the next few months at least!