An urban planner once told me that every car requires at least four times as much space as they actually occupy. Each needs a spot on the roads, and three available parking spaces: one at home, one at work, and one to shop. Motorcycles are much smaller, but they still spend most of their time parked.

Motorcycles are the primary means of transport in Southeast Asia, and learning to safely drive one is an essential part of adapting to life here. Assuming it’s not pouring rain and you’re not flooded past your ankles, it’s actually quite a pleasant experience… until you have to park.

Unlike the parking lots you may be familiar with, there’s no expectation that your bike won’t be moved. In fact, it might very well end up on another floor, in another parking lot, or behind hundreds of impassable parked bikes on the roof. In the latter case, the attendant will shrug and suggest you come back in a few hours. Eventually, this won’t even register as a frustration – you will simply reason that there are plenty of other things that are more convenient here, like the weather (recent typhoon aside) or unlimited symmetrical fiber to the home for USD 5 a month.

That being said, with a little technology the problem could be lessened a bit while waiting for automated parking lots to become commonplace. On rare occasions I see people with little radio emitters that make their headlights flash, but they’re not terribly common here and require carrying yet another thing on my already full key chain (homes here typically use several different locks). It seemed pretty easy to pull off something similar using my smart phone with an ESP8266 running NodeMCU. I had been meaning to try out the sleep modes to save battery power anyway, so off I went.

Signalling When a Specific Network Appears

One of the cool things you can do with NodeMCU is scan for a particular Wi-Fi network without connecting to it, and run a function if that network is detected:

scan_cfg = {} scan_cfg.ssid = "my_network" scan_cfg.show_hidden = 1 wifi.sta.getap(scan_cfg, 1, my_function)

On scanning, if ‘my_network’ is detected, then the function ‘my_function’ is called. Logically, all I would have to do is turn on my phone’s wireless hotspot, and call a function that flashes a light upon detecting it. However, the current draw while doing that was about 80mA. I wasn’t going to connect this to the motorcycle’s electrical system, as I wanted to possibly share it between bikes or other objects… so I needed to use sleep functions to conserve power.

Sleep Modes of the ESP8266

There are three sleep functions provided by NodeMCU: modem sleep, light sleep, and deep sleep. A good summary is available here, but in short: modem sleep just turns off the Wi-Fi, light sleep additionally disables the system clock and idles the CPU, and deep sleep turns off everything but the system clock.

Since I don’t mind waiting a little to find my motorcycle, scanning every 20 seconds seemed reasonable. The first step was to connect pins D0 and RESET on the mini D1, so the chip could pull itself out of deep sleep mode when needed. Note that the deep sleep timer functions in microseconds, not in milliseconds like the other timers in the program. After some trial and error, I discovered that allowing the scan to run for 2.5 seconds was reliable enough to find my phone (2 seconds failed occasionally):

scan_cfg = {} scan_cfg.ssid = "my_network" scan_cfg.show_hidden = 1 wifi.sta.getap(scan_cfg, 1, listap) function sleeping() node.dsleep(20000000) end function listap() – code to run when access point found goes here end tmr.alarm(0,2500,0,sleeping)

That ran pretty well, and with my multimeter I calculated an average current consumption of around 8mA. I had bought a pair of possibly genuine Panasonic 2600mAh cells, and a run time of about 27 days seemed quite nice. In fact the run time should be a bit less, not only because battery capacity claims are often exaggerated, but also due to the dropout voltage of the 3.3v voltage regulator on the Wemos mini D1. It uses an RT9013 low-dropout voltage regulator (PDF), so that means that at the currents we’re using, the device will lose power when the battery voltage drops to 3.35v or so. According to the LDO manufacturer’s Lithum-ion discharge curve example, I’ve used most of the stored energy in the battery by the time it reaches that voltage:

Finding the battery life acceptable, I used a 2N2222 transistor to control a 1W red LED. I was a little concerned about overheating the transistor during testing (when the light was on for longer periods), so I crimped a piece of scrap brass sheet around it.

The power resistor could have been much smaller, and the wire a lower gauge, but I had leftover parts to use and it seemed good enough. After testing a few different timings, I found that flashing the LED for 12ms every half second for 10 seconds was quite visible. After those 10 seconds, it calls the deep sleep function:

function listap(t) print "Found" tmr.alarm(2,10000,0,sleeping) tmr.stop(0) tmr.alarm(1, 500, 1, function () if on == 0 then tmr.interval(1, 12) gpio.write(pin, gpio.HIGH) on = 1 else tmr.interval(1, 500) gpio.write(pin, gpio.LOW) on = 0 end end) end

On completing a period of deep sleep, the device resets itself, happily going through the whole procedure again. After a few hours of irritating flashing lights on my desk, I concluded it worked reasonably reliably and it was time to put it in a box and install it on my bike. The box could have been much smaller, but I have a lot of these boxes to use:

Since I might want to use the device on other bikes or things, and also because I’m about the worst mechanic you’ll ever meet, I just threw it under the seat for now. There’s enough room in there, the temperature stays within safe ranges, and it’s reasonably protected from the elements. Note that the battery holder has been rewired to work in 1S2P configuration — if I put another cell in there they will be in parallel, not in series.

In practice, the range was acceptable, but in daylight the LED wasn’t very visible. On the other hand, in broad daylight an EFF sticker would have been sufficient to spot my bike. In dark parking lots or at night, the light is bright enough without being obnoxious. The full program was as follows:

print "starting" wifi.setmode(wifi.STATION) wifi.setphymode(wifi.PHYMODE_B) tries = 0 pin = 2 gpio.mode(pin, gpio.OUTPUT) on = 0 function listap(t) print "Found" tmr.alarm(2,10000,0,sleeping) tmr.stop(0) tmr.alarm(1, 500, 1, function () if on == 0 then tmr.interval(1, 12) gpio.write(pin, gpio.HIGH) on = 1 else tmr.interval(1, 500) gpio.write(pin, gpio.LOW) on = 0 end end) end scan_cfg = {} scan_cfg.ssid = "my_network" scan_cfg.show_hidden = 1 wifi.sta.getap(scan_cfg, 1, listap) function sleeping() node.dsleep(20000000) end tmr.alarm(0,2500,0,sleeping)

Tips for Future Use

A note to everyone using init.lua as a delay before loading your program (it makes debugging much easier): once satisfied with the device performance, remember to replace init.lua with your finished code. Often I don’t bother as it’s quite a convenience and a few extra seconds of boot time generally don’t matter to me, but here it would significantly increase your power consumption as the device would spend more time out of sleep mode. Every time it resets after sleep it would spend a few seconds in your init.lua delay loop.

In hindsight, I can think of three ways to make this project more practical. First, use a green LED as it would be more visible, because our eyes are more sensitive to green light. Second, using an ESP8266 board with an antenna connector (such as the mini D1 Pro) would increase the range significantly without impacting cost much. Finally, I didn’t at all consider where the flashing light should go: hanging it randomly off the seat offered poor visibility, and it isn’t much extra work to run a wire along the bike to a more visible location such as near the headlights, possibly sticking the LED on with a magnet for easy removal.

But any way you look at it, I accomplished my goal. Now all I need to do to locate my motorcycle is turn on my phone’s hotspot and look for the blinking red light.