Customising The E-ink Weather Display

With all the pieces in place to allow me to draw to the e-ink screen, I made some 8-bit RAW images and wrote values to the display. It worked, but it looked a bit crusty, not least because the Framebuffer.text method for putting text onto the screen is a fixed size, and a fixed font. Remember that I’m aiming for something like this, which uses a few different font sizes:

As with so many things, this has been solved before, with a piece of work that I think is absolute genius going by the catchy title of “font_to_py“. Basically, it’s a Python command line tool that inputs a font file (like a ttf) and some parameters, such as pixel size, and outputs a Python file containing all the data necessary to render the font as byte data. The files are very small too, with a 20 pixel font “compiling” down to 17kb.

Then you just import the Python files, which in my case were three different sizes of Calibri, and the functions to draw the font to a Framebuffer:

import calibri_36, calibri_15, calibri_12
from writer import Writer

And to draw to the display something like:

wri = Writer(display_buffer, calibri_36)
Writer.set_textpos(display_buffer, text_top, text_left)
wri.printstring(max_daily, True)

To print the value of the maximum daily temperature to the screen (via the Framebuffer). Somewhat strangely, the coordinates are passed in as (top, left) rather than (x, y). I also tweaked writer.py slightly to make sure it handled transparency (i.e. 0 pixels), by changing:

self.device.blit(fbc, s.text_col, s.text_row)

to

self.device.blit(fbc, s.text_col, s.text_row, 1 if invert else 0)

in the _printchar method.

The fonts still looked a little weedy, though, and rather than make a bold version of Calibri my own slightly hacky method was just to print the same characters four times over, offset by 1 pixel each time:

Writer.set_textpos(display_buffer, text_top, text_left)
wri.printstring(max_daily, True)
Writer.set_textpos(display_buffer, text_top, text_left - 1)
wri.printstring(max_daily, True)
Writer.set_textpos(display_buffer, text_top + 1, text_left)
wri.printstring(max_daily, True)
Writer.set_textpos(display_buffer, text_top + 1, text_left - 1)
wri.printstring(max_daily, True)

The results were good enough.

For degree symbols I used Framebuffer.ellipse, and to calculate the position to draw the symbol in writer.py even provides a very useful “how wide is this piece of text?” function:

text_width = get_string_display_width(wri, max_daily)
display_buffer.ellipse(text_left + text_width + 4, text_top + 5, 4, 4, 0)
display_buffer.ellipse(text_left + text_width + 4, text_top + 5, 3, 3, 0)
display_buffer.ellipse(text_left + text_width + 4, text_top + 5, 2, 2, 0)

Note that the ellipse (circle) is also drawn three times to create a faux bold effect.

The last part was some trigonometry as I wanted to display wind direction as an arrow in a circle.

Putting it all together resulted in something usable, albeit with some pixel tweaking required to sharpen some of the smaller images:

An epaper display showing temperature, weather summary, wind direction and rainfall duration

The last thing was to schedule it to update every hour. I experimented with lightsleep and deepsleep, which are supposedly low power modes, but the Pico just never seemed to wake up (and from Googling that appears to be a known issue), so I just took to a sleep function:

HOURS_SLEEP = 1
sleep_time = 60 * 60 * HOURS_SLEEP

Having made something functional, I now had a list of things I wanted to improve:

  1. The size. It’s a little bit too small. I decided to order a 2.9inch e-paper display, and this time get one with the right HAT
  2. The four hour summary is useful, but the weather at 7am is irrelevant once that time of day is passed. I wanted to change it to a rolling twenty-hour period
  3. What happens if I hit an error? I might think the weather’s the same every day and not notice the program has crashed. I needed to show the date/time of last update, and the “feels like” temperature is probably the least useful metric to replace it with
  4. The sleep function updated after an hour, but every update took maybe twenty seconds. If I was going to show update time, I wanted it to be on the hour, not to drift by a couple of minutes per day

I placed an order for another Pico (W, with headers, of course) and the larger e-ink display and waited.

Show Me The Weather

After connecting the e-ink display to the Pico and managing to get it to display in landscape mode I felt I’d solved the unknowns in making this e-ink weather display. Now it was time to decide what I was going to show, and how I was going to show it.

One Python example I’d found used a weather API called OpenWeather, so I went and signed up. Yet no matter what I seemed to do in terms of requesting API keys, and even waiting a day for activation, I was never able to make a valid request. In the meantime, with a little more Googling I found Meteo’s API. It turned out to be very easy to work with and had everything I’d need.

I chose some data fields I wanted to display and opened Photoshop to start a basic layout.

Design for an e-ink weather display showing maximum and minimum daily temperature, overall weather, wind gusts, and a summary for every 4 hours

I wanted minimum and maximum temperatures during the day, a nice summary icon, average wind speed, and then a bit more info about how it would feel which is where the 9 degrees (“feels like” temperature), 20mph (wind gusts), and 2hrs (duration of rainfall during the day) came from. Below that is a little summary of what the weather looks like at four hour intervals..

The API call is just a request to a URL, such as this:

https://api.open-meteo.com/v1/forecast?latitude=XXXXX&longitude=XXXXX&hourly=temperature_2m,windspeed_10m,winddirection_10m,windgusts_10m,weathercode&daily=temperature_2m_min,temperature_2m_max,sunrise,sunset,windspeed_10m_max,windgusts_10m_max,winddirection_10m_dominant,precipitation_hours,apparent_temperature_max,weathercode&timezone=GMT&windspeed_unit=mph

You can request more fields in either the hourly or daily summary, and the documentation was pretty clear too.

The return is JSON and easily parsed with Micropython. I wrote a small module to do it all:

import network, rp2, time
import urequests
import json
import sys
from io import BytesIO

LATITUDE = 'XXXXX'
LONGITUDE = 'XXXXX'

BASE_URL = 'https://api.open-meteo.com/v1/forecast?' 
URL = BASE_URL + 'latitude=' + LATITUDE + '&longitude=' + LONGITUDE + '&hourly=temperature_2m,weathercode&daily=temperature_2m_min,temperature_2m_max,sunrise,sunset,windspeed_10m_max,windgusts_10m_max,winddirection_10m_dominant,precipitation_hours,weathercode&timezone=GMT&windspeed_unit=mph'

# API documentation at https://open-meteo.com/en/docs
class Meteo:
    def __init__(self):
        self.json = None
        return
    
    def get_data(self):
        # Make GET request
        response = urequests.get(URL)
        if response.status_code != 200:
            print("Error")
            print(response.status_code)
            print(response.content);
            response.close();
            raise Exception("Error getting weather data " + response.status_code)
        else:
            self.json = response.json();
            response.close();
            return self.json;

I’d call it from a function, just so it was easier to test:

def getWeatherData():
    weather = meteo.Meteo();
    weather.get_data();
    return weather;

And accessing the data is just like accessing any Python object e.g. to get the maximum daily temperature:

max_daily = str(round(weather.json["daily"]["temperature_2m_max"][0]))

So far so simple, and I was quickly on to drawing all of this to the display, which is the subject of the next post.

Micropython E-ink Display Rotation

By default, sending pixels from a Raspberry Pico to a Waveshare e-ink display will result in a portrait image. In the case of the 2.13inch display I started with, this means a screen of 122 pixels wide by 250 pixels high.

But what if you want to display in portrait mode? As someone who’s used to the simple world of CSS it was actually fun to get back into the world of bits and pixels again.

Just like the ZX Spectrum, where I started programming, the display is stored as a list of bytes where each byte is eight consecutive pixels, either set to 1 to display a dark pixel or 0 for white (or clear).

In Micropython, this is stored as a byte array. So the code that sets up the display looks like this:

self.buffer_black = bytearray(self.height * self.width // 8)

Thankfully, there’s a helper module in the shape of Framebuffer which makes changing the value of these pixels much easier. It contains methods to write text, lines, rectangles and circles and, most usefully, to draw images at arbitrary coordinates using the blit command.

Not only does blit make it easier to draw, say, a 50×50 image onto the 122×250 display, but it also makes it much easier to draw an 8-bit image onto the 1-bit display. You start by setting a Framebuffer object with its data source as the byte array, such as:

self.imageblack = framebuf.FrameBuffer(self.buffer_black, self.width, self.height, framebuf.MONO_HLSB)

Where the first argument is the byte array, with and height are self explanatory, and framebuf.MONO_HLSB means that this framebuffer is a mono (1-bit) image, with horizontal bits stored as a byte.

To draw an image on top, assuming you have the pixel data in a variable called image_buffer, you can do something like:

self.imageblack.blit(framebuf.FrameBuffer(image_buffer, width, height, framebuf.GS8), x, y, key)

Where width and height are the width and height of the image, framebuf.GS8 signifies an 8-bit grayscale image, x and y are the coordinates to draw to, and key tells the blit method which pixels in the image it should treat as transparent. This allows you to draw just the black pixels onto the screen without the “white” pixels (or 0 bits) overwriting anything that’s already on the screen. Because the frame buffer takes the image_buffer bytearray as a parameter but changes the pixels in it by reference, any changes to self.imageblack (which is a Framebuffer object) are applied to the bytearay in self.buffer_black – which represents the actual pixel data.

So far so simple as far as drawing an image to the screen goes, assuming you’re still working in portrait mode. I made myself a helper function to load raw files from the on-board memory:

def drawImage(display_buffer, name, x, y, width, height, background=0xFF):
    image_data = get_image_data(name)
    if image_data:
        display_buffer.blit(image_data, x, y, background)
    else:
        image_buffer = bytearray(width * height)
        filename = 'pic/' + name + '.raw'
        with open (filename, "rb") as file:
            position = 0
            while position < (width * height):
                current_byte = file.read(1)
                # if eof
                if len(current_byte) == 0:
                    break
                # copy to buffer
                image_buffer[position] = ord(current_byte)
                position += 1
        file.close()
        display_buffer.blit(framebuf.FrameBuffer(image_buffer, width, height, framebuf.GS8), x, y, background)

Drawing a landscape image instead of portrait should be as simple as:

  1. Set up a Framebuffer with a width equal to the e-ink display height, and a width equal to the e-ink display width
  2. Draw to the Framebuffer
  3. Transpose columns of pixels into rows in a new bytearray
  4. Send to the e-ink display

It seemed like exactly the sort of thing that someone would already have tackled, and so it proved. So with some Googling and adjustment of width/height variables to suit the display I was working with I came to this:

def rotateDisplay(display):
    epaper_display = bytearray(epd.height * epd.width // 8)
    x=0; y=-1; n=0; R=0
    for i in range(0, epd.width//8):
        for j in range(0, epd.height):
            R = (n-x)+(n-y)*((epd.width//8)-1)
            pixel = display[n]
            epaper_display[R] = pixel
            n +=1
        x = n+i+1
        y = n-1
    epaper_buffer = framebuf.FrameBuffer(epaper_display, epd.width, epd.height, framebuf.MONO_HLSB)
    return epaper_display;

And I have to confess I didn’t worry too much about the details. Not until I ran it and the output was complete garbage.

It took a while to work out what I was doing wrong, but once I found the problem the solution made complete sense: I was inputting a Framebuffer stored as MONO_HLSB but the function above transposed the position of bytes, not individual bits. In other words, chunks of 8 pixels were being moved to the right place on the screen, but forming a column rather than a row. No wonder it looked bad.

The solution was simple: start with a Framebuffer of the type MONO_VLSB, where the bytes represent a column of 8 pixels, not a row. Then the transposition naturally mapped to MONO_HLSB.

I ended up with something like this:

display = bytearray(epd.height * epd.width // 8)
    display_buffer = Screen(display, epd.height, epd.width, framebuf.MONO_VLSB)
    display_buffer.fill(0xff)
    --- 
      Draw somethings here
    ---
    epaper_display = rotateDisplay(display)

where Screen was an extension of the Framebuffer class, necessary because the width and height parameters weren’t accessible in a native Framebuffer object. It just felt nicer to store them “in place” rather than pass another parameter around.

class Screen(framebuf.FrameBuffer):
    def __init__(self, display, width, height, encoding):
        self.display = display
        self.width = width
        self.height = height
        self.encoding = encoding
        return

The final wrinkles was that the e-ink display was 122 pixels “wide” (or now 122 pixels high, as I was trying to work in landscape mode), and therefore not a number that formed a whole number of bytes. Because of the way I was treating the screen, it meant my top left origin was at the coordinates (0, 6), which felt a bit nasty but was something I could live with. The alternative would have been to transpose the whole thing down 6 pixels using another Framebuffer, but frankly I couldn’t be bothered.

Writing To The E-ink Display With Micropython

I’d gotten as far as running a few lines of code on the Pico, so the next thing was to work out if I could display anything at all on the e-ink display. Since I’d already messed up by buying the wrong HAT type, I still had a small doubt about whether I would get the two to work together at all.

Waveshare has its own Github respository so I headed there to track down the correct Python file. I knew I needed one of the files that started with “Pico_ePaper-2.13-“, but which one?

There was a little stick on the display telling me I had a “v3”, but working out whether I had an A, B, C or D model was a mixture of guesswork and deduction. The file for both B and C appeared to support multiple colours, which is not what I had, so it wasn’t those. I had nothing else to go on, so I tried both A and D. Neither seemed to work properly, although I did get the screen to flicker a bit so something was happening.

I can’t even remember how I finally got the thing working, but it appears to be through a hybrid of files that started as Pico_ePaper-2.13-B_V4.py with all the code that referred to the red image data removed. The whole process was made more difficult by the fact that I seemed to have messed up connecting two of the pins, having not spotted that there was a pin labelled “GND” right between “GP21” and “GP22” on the Pico board.

This was my “driver” module:


from machine import Pin, SPI
import framebuf
import utime

EPD_WIDTH       = 122
EPD_HEIGHT      = 250

RST_PIN         = 12
DC_PIN          = 8
CS_PIN          = 9
BUSY_PIN        = 13

class EPD:
    def __init__(self):
        self.reset_pin = Pin(RST_PIN, Pin.OUT)
        
        self.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP)
        self.cs_pin = Pin(CS_PIN, Pin.OUT)
        if EPD_WIDTH % 8 == 0:
            self.width = EPD_WIDTH
        else :
            self.width = (EPD_WIDTH // 8) * 8 + 8
        self.height = EPD_HEIGHT
        
        self.spi = SPI(1)
        self.spi.init(baudrate=4000_000)
        self.dc_pin = Pin(DC_PIN, Pin.OUT)
        
        
        self.buffer_black = bytearray(self.height * self.width // 8)
        self.init()

    def digital_write(self, pin, value):
        pin.value(value)

    def digital_read(self, pin):
        return pin.value()

    def delay_ms(self, delaytime):
        utime.sleep(delaytime / 1000.0)

    def spi_writebyte(self, data):
        self.spi.write(bytearray(data))

    def module_exit(self):
        self.digital_write(self.reset_pin, 0)

    # Hardware reset
    def reset(self):
        self.digital_write(self.reset_pin, 1)
        self.delay_ms(50)
        self.digital_write(self.reset_pin, 0)
        self.delay_ms(2)
        self.digital_write(self.reset_pin, 1)
        self.delay_ms(50)


    def send_command(self, command):
        self.digital_write(self.dc_pin, 0)
        self.digital_write(self.cs_pin, 0)
        self.spi_writebyte([command])
        self.digital_write(self.cs_pin, 1)

    def send_data(self, data):
        self.digital_write(self.dc_pin, 1)
        self.digital_write(self.cs_pin, 0)
        self.spi_writebyte([data])
        self.digital_write(self.cs_pin, 1)
        
    def ReadBusy(self):
        print('busy')
        while(self.digital_read(self.busy_pin) == 1): 
            self.delay_ms(10) 
        print('busy release')
        self.delay_ms(20)
        
    def TurnOnDisplay(self):
        self.send_command(0x20)  # Activate Display Update Sequence
        self.ReadBusy()
        
    def TurnOnDisplayPart(self):
        self.send_command(0x20)        
        self.ReadBusy()

    def SetWindows(self, Xstart, Ystart, Xend, Yend):
        self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
        self.send_data((Xstart>>3) & 0xFF)
        self.send_data((Xend>>3) & 0xFF)

        self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
        self.send_data(Ystart & 0xFF)
        self.send_data((Ystart >> 8) & 0xFF)
        self.send_data(Yend & 0xFF)
        self.send_data((Yend >> 8) & 0xFF)
        
    def SetCursor(self, Xstart, Ystart):
        self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
        self.send_data(Xstart & 0xFF)

        self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
        self.send_data(Ystart & 0xFF)
        self.send_data((Ystart >> 8) & 0xFF)
    

    def init(self):
        print('init')
        self.reset()
        
        self.ReadBusy()   
        self.send_command(0x12)  #SWRESET
        self.ReadBusy()   

        self.send_command(0x01) #Driver output control      
        self.send_data(0xf9)
        self.send_data(0x00)
        self.send_data(0x00)

        self.send_command(0x11) #data entry mode       
        self.send_data(0x03)

        self.SetWindows(0, 0, self.width-1, self.height-1)
        self.SetCursor(0, 0)

        self.send_command(0x3C) #BorderWavefrom
        self.send_data(0x05)

        self.send_command(0x18) #Read built-in temperature sensor
        self.send_data(0x80)

        self.send_command(0x21) #  Display update control
        self.send_data(0x80)
        self.send_data(0x80)

        self.ReadBusy()
        
        return 0       
        
    def display(self):
        self.send_command(0x24)
        for j in range(0, self.height):
            for i in range(0, int(self.width / 8)):
                self.send_data(self.buffer_black[i + j * int(self.width / 8)])   
        self.TurnOnDisplay()

    
    def Clear(self, colorblack):
        self.send_command(0x24)
        for j in range(0, self.height):
            for i in range(0, int(self.width / 8)):
                self.send_data(colorblack)
        self.TurnOnDisplay()

    def sleep(self):
        self.send_command(0x10) 
        self.send_data(0x01)
        
        self.delay_ms(2000)
        self.module_exit()
        

I haven’t yet managed to track down what all the send_command statements actually do, but I feel sure there must be some documentation for them somewhere.

Anyway, finally, I had the equivalent of “hello world” on the display, without really knowing how. Also, I had discovered that the epaper display naturally preferred to display in portrait mode, which wouldn’t work for the layout I had in mind for my weather display.

    epd = EPD()
    epd.Clear(0xff)
    epd.imageblack = framebuf.FrameBuffer(epd.buffer_black, epd.width, epd.height, framebuf.MONO_HLSB)
    
    epd.imageblack.fill(0xff)
    #epd.imagered.fill(0xff)
    epd.imageblack.text("Waveshare", 0, 10, 0x00)
    epd.imageblack.text("ePaper-2.13-B", 0, 25, 0x00)
    epd.imageblack.text("RPi Pico", 0, 40, 0x00)
    #epd.imagered.text("Hello World", 0, 55, 0x00)
    epd.display()
    epd.delay_ms(2000)
    
    epd.imageblack.vline(10, 90, 40, 0x00)
    epd.imageblack.vline(90, 90, 40, 0x00)
    epd.imageblack.hline(10, 90, 80, 0x00)
    epd.imageblack.hline(10, 130, 80, 0x00)
    epd.imageblack.line(10, 90, 90, 130, 0x00)
    epd.imageblack.line(90, 90, 10, 130, 0x00)
    epd.display()
    epd.delay_ms(2000)

It felt like I’d conquered most of the unknowns anyway, so now onto showing some actual weather.

Getting Started With The Raspberry Pi Pico

Raspberry Pi Pico

The whole Raspberry Pi craze has passed me by before. Mainly because I have no competency whatsoever in electronics, and when it comes to using it just as a small computer I’ve had no need for one.

I don’t really know what drew me to the Raspberry Pi Pico, but it just looked like such a fun little thing to experiment with. I think it’s partly that it’s not just a small computer, because there’s no keyboard or mouse input, no monitor, and the only way to program it (that I know of so far) is through an attached computer. And with a very low power consumption it comes across more as a component to be used in something else than a mini-computer. Plus it can be programmed in Micropython, which is more-or-less a subset of the Python language with which I’m familiar (although no expert, but it looks pretty much the same as every other language to be honest).

But given my previously mentioned lack of electronics competency that alone wouldn’t be enough to lure me in, but the more I started to look at it the more it appeared that competency in electronics really wasn’t al that important. So many components, whether e-ink or LCD displays, cameras or motion sensors seem to be almost plug-and-play. Even I couldn’t mess that up.

Finally, I’d recently installed Hive Smart Radiators, and although the app is okay for controlling them, I’d been thinking how nice it would be to have small, unobtrusive controls for them in key rooms so it’s harder to forget to turn heating off in rooms that aren’t going to be used for most of the day, or turning them back on again if someone comes home early. I’d looked at cheap Android tablets that I could then build an app for, but for a kind of “at a glance” display I’d either have to have the screens always on, or look at something more expensive like OLED. Android, or cheap old model Apple tablets, just seemed like overkill, but also lacking in flexibility.

So a Pico looked fun and an avenue to explore, but where to start?

Starting Small: An E-ink Weather Display

The thing that piqued my interest was tiny e-ink displays, like the ones from Waveshare. I knew they’d be low power and ideal for infrequently updated displays, and although maybe not quite what I wanted for smart radiator controls (but then maybe they will be), I couldn’t get the idea of building something with one out of my head.

Waveshare 2.13 inch e-ink display

I decided I’d start with something smaller than a heating controller and settled on a weather display. My partner is always asking me if she’s going to need an umbrella that day, despite having a smartphone sitting in front of her, so I could kid myself it would be useful to install by the front door. Plus, some of the first search results I’d pulled up when looking at how hard it might be to program an e-ink were weather displays. (Albeit for a Raspberry Pi, not the Pico, and with a larger screen than I was planning. But it was some working code to give me direction.)

I had my project, and it looked achievable, so I just needed to buy some kit and start playing.

Buying The Right Equipment

In trying to buy just a Pico and an e-ink display my lack of knowledge almost set me back. Just in time, I realised that I needed a Pico W, which is a Pico with an onboard wifi controller, otherwise my weather display wouldn’t be able to retrieve weather information. (Although it’s possible to add an external controller, it’s much easier just to have it there at the start.)

Secondly, I needed a Pico with soldered headers to make the connection to the display much easier. In fact, as I don’t (yet) own a soldering iron, it was the only way to make the connection possible. The Pico with soldered headers costs a few pounds more, but easily worth the extra convenience. As shown in the image below, the headers are the “spikes” that make it easy to attach cables or other devices.

Raspberry Pi Pico with soldered headers

I knew then that if I bought an e-ink display with a HAT connection (which apparently stands for “hardware attached on top” and is easy to remember because it really does look lik you’re putting a hat on it) all I’d have to do is drop it on top.

The first confusion came when I tried to attach the two. The Pico had two rows of pins, but the display only had a single connection row. It had me stumped for a while, but then I worked out that I’d bought the wrong type of HAT. Apparently the e-ink display I’d bought hat a HAT configuration for a standard Raspberry Pi, not for a Pico.

This is what not to buy for a Pico:

Waveshare e-ink display with Raspberry Pi HAT connection

Whereas what I wanted would attach like this:

Waveshare e-ink display attached via HAT to Raspberry Pi PIco

It wasn’t a disaster, though as it also came with a cable connection. It just came down to working out how on earth to join the two things together.

A cable for a Waveshare e-ink display

Googling for the connections wasn’t too hard, as it’s all on the Waveshare site, but connecting them was made harder than it should be due to failing close-up eyesight which meant that matching microscopic pin numbers on the Pico to coloured cables from the display wasn’t as easy as it could have been.

Cable connections from a Raspberry Pi Pico to Waveshare e-ink display

Installing the Micropython firmware and connecting to the Pico with the Thonny IDE was straightforward and I was able to run an example piece of code to turn the onboard LED on and off within a few minutes. But I had a feeling that getting the e-ink display to show anything was going to be a little harder. For various reasons, so it proved, but that’s going to be covered in the next post.

Accessing Cordova Config Info From JavaScript Without Plugins

I wanted to access the current build number for analytics, but there seemed to be no easy way to get at the info that Cordova puts into the plist file (Xcode) and AndroidManifest.xml (Android Studio).

My solution is to copy those to files into the www folder at build time and then load them with AJAX. It means adding the following line in the iOS project in ios/cordova/lib/copy-www-build-step.js, somewhere near the end:

shell.cp('-f', path.join(path.dirname(PROJECT_FILE_PATH), path.basename(PROJECT_FILE_PATH, '.xcodeproj'), '*.plist'), dstWwwDir);

As part of the build process, this copies the plist file into the root web directory as it’s deployed onto the device (meaning there are no changes to the source filesystem). So if it doesn’t seem to work, write some kind of check to see if the file actually gets copied.

Then in Android Studio, inside build.gradle for the app module, right before the line that starts android {:

task copyFiles(type: Copy) {
description = 'copying some file(s)....'
from "../app/AndroidManifest.xml"
into '../app/src/main/assets/www/'
}
preBuild.dependsOn copyFiles

Unlike the Xcode version, this copies AndroidManifest.xml into the www root directory before it’s deployed to the device, so the file will appear inside the www directory. It’s overwritten each time in case there are any changes.

Then inside JavaScript, to access data within those files I used jQuery to parse AndroidManifest.xml and a library called Plist Parser to make iOS just as easy. Plist Parser turns the annoyingly structured file into a JSON object that’s trivial to navigate through.

The JavaScript code, assuming there’s a check for platform somewhere else, is below. I mainly wanted CFBundleVersion (iOS) and android:versionCode (Android), but you can access anything else in the file too:

if (platform == 'android') {
$.ajax({
url: 'AndroidManifest.xml',
dataType: 'text',
success: function (response) {
var xml = $(jQuery.parseXML(response));
var root = xml.find('manifest');
if (root) {
if (root.attr('android:versionCode')) {
var app_version = root.attr('android:versionCode');
}
if (root.attr('package')) {
var app_id = root.attr('package');
}
}
},
error: function (response) {
//Will have to do without data from plist file
}
});
} else if (platform == 'ios') {
$.ajax({
url: '[your plist filename]',
dataType: 'text',
success: function (xmlString) {
var plist = PlistParser.parse(xmlString);
if (plist) {
if (plist['CFBundleVersion']) {
var app_version = plist['CFBundleVersion'];
}
if (plist['CFBundleVersion']) {
var app_id = plist['CFBundleIdentifier'];
}
}
resolve();
},
error: function (response) {
//Will have to do without data from plist file
}
});
}

I’ve yet to see whether running cordova build ios or cordova build android overwrites the build process changes or not.

Not Riding in Istria – Day 7

The final day was home day. There was no riding, and no horses involved, and we were all transferred to Pula after breakfast. For some of us it was for the whole day – a little two long – but for those with earlier flights it was only a couple of hours – a little too short.

Still, there was some good scenery to be had, and a little more about the town than Rovinj had – which was good as there was no way we could kill eight hours there if there wasn’t.

DSC00817

Before we set off I managed to take a photo of our base for most of the week. It was rather nice!

DSC00818

With an outside dining area, although it was too cool to use for all but lunch on the last day.

DSC00819

Anyway… on to Pula. It had a colosseum an amphitheater, of the old Roman type, which made for some good photos. It was also the hottest day of the week and, dare I say it, would have been too hot to ride a horse. Or too hot to be comfortable at least. Quite some contrast to deluge-day (as Monday will forever be known).

DSC00820

Ruins + sky.

DSC00821DSC00822

The airport was very close to the town, as you can see from this shot of the back end of a jet on final approach.

DSC00823

Patrick giving it all gladiator, just with a mobile phone and a rucksack instead of a sword and shield.

DSC00824

Although this looks more like the background should be on fire or exploding or something.

DSC00825

It was a great amphitheater, but spoiled a bit by lighting rigs and stages and such. On the one hand, I’m sure it’s a great venue to watch performances on the like, but on the other: I don’t care, it got in the way of my view.

DSC00826

Ann enjoying having her photo taken.

DSC00827

More arches.

DSC00828

There was a museum in the basement. It had a lot of pots. I’m told they’re called “amphora” but if it walks like a pot and quacks like a pot then it’s a pot.

DSC00829

One of the classier establishments in town. Trademark infringement sandwich anyone?

DSC00830

Not to be outdone by Rovinj, Pula also had a tower.

DSC00831

Although you couldn’t go up it, and it wasn’t attached to the church.

DSC00832

It was like the two buildings had had an argument or something.

DSC00833

The town square. It might look deserted, but I just got lucky with the photo. In early evening there was a torrent of weddings that poured forth from its doors and had photos taken in front of the old temple thing next door.

DSC00835

A temple thing which you had to pay to go into, despite it being more interesting on the outside.

DSC00837

Pula was on the coast, and I loved seeing this old container ship rusting away. I have no idea whether it was being dismantled, used for storage or repaired, but it was MASSIVE.

DSC00838

The back of the Temple of Overpriced Admission.

DSC00839

Which contrasted nicely with the bridge of the ship opposite.

DSC00841

We found a mosaic. It was harder to track down than you might think, and we only found it because an online guide said it was harder to track down than you’d think. Basically, follow signs, go into the car park, and then look for the place you’d least expect to find a Roman mosaic and you’re there.

DSC00842

The other half of the mosaic. It was something about a woman being punished for doing something bad by being tied to a cow. You can tell I was paying attention.

DSC00843

Some flowers. They were quite big, but you can’t tell in this photo.

DSC00844

You still can’t tell how big those flowers were even with a building for scale. That’s the trouble with perspective sometimes.

DSC00845

We went up to the castle and were welcome by an array of cannons.

DSC00846

And a very tiny Godzilla. For some reason, the sunglasses I took with me rendered most lizards invisible and I only saw them if someone else pointed them out and I took my sunglasses off.

DSC00847

Be careful of doing whatever bad thing is happening in this sign.

DSC00848

The view from the top of the castle.

DSC00849

The cannons from the other side. Not so scary now you’re all pointing in the wrong direction, eh?

DSC00850

Looking at the dock of the bay.

DSC00851

From the castle back to the amphitheater. It was a grand view and a very slow day and rather pleasant, right up until the flight delay, then getting to Gatwick and finding that despite checking before we left there weren’t very many trains to London, and then getting in at just shy of 4am. Still, I’m thankful for all night tube for the first time.

For me, the week I spent in Crotia has to go down one of the best trail rides I’ve been on. It may not have quite had the spectacular scenery of the ride in Jordan, but it was well organised, we were well fed, we had nice beds each night, the horses were good to ride yet still even-tempered, and Petar looked after us very well. Plus, we saw a little of that part of historic Croatia, a country that I knew nothing about before and now know almost nothing about.

All posts in this holiday:

Riding in Istria (Croatia) – Day 1

Riding in Istria – Day 2

Riding in Istria – Day 3

Riding in Istria – Day 4

Riding in Istria – Day 5

Riding in Istria – Day 6

Riding in Istria – Day 6

Our last half a day of riding was upon us, and a visit to a coastal town awaited that afternoon.

DSC00783

Patrick tacking up.

DSC00784

Alice also tacking up.

DSC00785

Apache having been tacked up. Finally I was starting to get the hang of things.

DSC00787

We rode to a nearby town, one which we’d had dinner at two nights before, and paraded around the streets on horseback like we owned the place.

DSC00788

It’s like we’re ye olde knights or something.

DSC00789

Taking the horses back and letting them into the field I saw why it took so much effort to brush Apache each morning.

DSC00790

Yup – that’s my horse upside down in the middle. Well, it wasn’t my problem any more, but it was nice to see him having a little roll around after putting up with me for six days.

DSC00791

In the next field was a young foal which Patricia managed to attract over.

DSC00793

Ann + Patricia + mini horse.

DSC00794

The afternoon involved a trip to Rovinj. Close to Italy, even some of the signs were in Italian (including “Rovigno” for the name of the town, which sounded nothing like “Rovinj” which Petar said it as, with a hard “j” on the end so it sounded like it would rhyme with “hingie”, if that was a word. I suspected he was having us on and the “j” was more of a “y” sound so I never tried repeating it in front of a native, and TripAdvisor seems to back me up here: https://www.tripadvisor.co.uk/ShowTopic-g303833-i8813-k8396513-Silly_question_but_need_to_ask-Rovinj_Istria.html).

DSC00795

There was a church. It had a tower.

DSC00796

This tower, in fact.

DSC00797

You could climb the tower via a lot of wooden stairs with open risers. Ann came and it took quite some time.

DSC00798

And the view back down was like something from Escher.

DSC00799

But the view from the top was totally worth it.

DSC00800

Looking north(ish) from Rovinj.

DSC00801

Zooming in, I’ve no idea what the town in the distance is, or the horrible complex in the foreground. Unlike the previous juxtaposition of old and new, this is not art.

DSC00802

There were a lot of narrow alleyways that led right down to the water.

DSC00803

And it all looked pretty full-on Mediterranean.

DSC00804

Roofs.

DSC00805

What looks like a cemetery.

DSC00806

The tower had a bell. I don’t know if it ever rang, but I don’t think they did while tourists were up there.

DSC00807

Another view back down the tower stairs. Descending took just as long as climbing.

DSC00808

Colours.

DSC00809

A little staircase covered in shells. It was one of those towns to just wander around and find things.

DSC00810

Things like a statue of a boy holding a fish that’s swallowed a wine bottle.

DSC00811

That’s also a fountain.

DSC00812

A fountain with fishes on the side.

DSC00813

A view from the south(ish) side of the harbour and looking back at the church tower. It was a very compact old town and you could do a lap of it in fifteen minutes.

DSC00814

One of those alleyways down to the sea.

DSC00815

And from the alleyway there were views of Rovinjians (if that’s what they’re called) going about the business of fishing.

We spent about three hours there, which was plenty of time for walking, climbing the tower, finding and eating ice-cream, drinking coffee and then wondering what else to do. Maybe half an hour too long, but it was a nice town and a nice change from sitting on a horse.

Next: Day 7

Riding in Istria – Day 5

This was to be our last full day of riding. So, after a short van ride to pick up our horses and saddle up were were off again.

DSC00738

Although our guide for the day, who may have been called Jakov (that’s the closest Google can tell me to what we were calling him) decided to walk for some of it. I think he spent plenty of time on a horse and only really saw the point of it if it was easier to ride. For us, we were paying to be on a horse, and goddammit we were going to say on a horse no matter how uncomfortable it was. And, after four long days of riding, it really was.

DSC00739

Horses In The Distance, with a blue sky!

DSC00740

One riderless horse in the distance.

DSC00742

I usually had an idea of when some trotting was coming on as Jacov, or Petar if he was leading, would look back to see how far behind I was. In this case: pretty far.

DSC00743

So I put a bit of a trot in to catch up. There are horses in this picture but they’re very well hidden in the shadows.

DSC00744

After some trotting, I could begin to drop back again.

DSC00745

And drop back a bit further.

DSC00746

Yup – that’s about far enough.

DSC00747

We saw plenty of birds of prey. I have no idea what they were, but this is one of them.

DSC00748

And as we approached our lunch stop the scenery got interesting with rocks and wotnot sticking out of the trees.

DSC00749

Not that Apache seemed to care. He’s seen it all before, and you can’t eat rocks.

DSC00750

It sort of looks like a bony structure sticking out of the hill.

DSC00751

The castle in the distance was to be our stopping point.

DSC00752

This was the best I could take of it from a distance with a zoom lens on a moving horse.

DSC00754

Another rock sticking out of the trees.

DSC00755

You can lead a horse to water but you can’t make it drink.

DSC00756

Or can you?

DSC00757

The castle, close up. The horses had been tied up at the bottom of the hill in a field so they didn’t get to see this. As I mentioned before, you can’t eat rocks so they probably didn’t care.

DSC00758

Apparently this had been the site of a fort since the 6th century.

DSC00759

Applying my trained architectural eye, I could certainly validate that it was old.

DSC00760

Really quite old.

DSC00761

There was probably something somewhere telling us that this was a great hall or whatever. Use your imagination and add a roof and the rest of the walls.

DSC00762

But it made some nice photos, old or not.

DSC00763

Another view of the castle. I have no idea when I took this and why it’s so far away.

DSC00764

A luminous green grasshopper took up a brief residence on the back of my chap. Again, like that fidgeting spider, it didn’t really sit or stand still long enough to get an in-focus shot.

DSC00765

Like a stone hand poking out of the trees.

DSC00766

An oven inside the grounds of the castle. It’s also officially old.

DSC00767

Zooming in to a bird of prey. I’m not sure what the markings signify but I’m going to take it as an RAF bird and hence friendly.

DSC00768

More moody castle shots.

DSC00769

Another semi-moody castle shot.

DSC00770

The whole complex was quite large and there was a very laissez-faire attitude to scrambling over walls to look at things.

DSC00771

Although this bit looked one scramble too far as there was quite a drop down one side. I am, however, quite pleased with the juxtaposition with the modern bridge in the background. It’s art, innit?

DSC00772

Ann + castle.

DSC00773

Me + Ann + part of castle. (Thanks Patrick!)

DSC00775

A toweringly towering tower towers.

DSC00776

Patricia was casually sat in an old window so I decided to take a photo. Then she got up as I was fiddling with the camera and I had to get her to un-casually sit back down again.

DSC00777

And we were off again, climbing up and down.

DSC00778

Patrick just as his horse hit the accelerator pedal.

DSC00779

Ann + horse eating grass.

DSC00781

Me + horse eating grass.

DSC00782

We trotted over a few level crossings. None of the horses stopped, despite the signage. Can’t they read?

We ended our trail that night with the horses safely returned to home. We still had half a day to ride tomorrow, but it felt like we’d all made it around Istria safely and, apart from the damp day, quite happily.

Next: Day 6.