Project In A Day: When Is My Next Bus?

platform.JPG
Made using only components from my stock !

Why This Project?

For once, this project was not for me... it was for my wife !
Every morning she takes the bus then train to go to work. If she misses her train, she has to wait for more than 30 minutes for the next one. Not missing her bus is therefore quite important.
Where we live every bus station has a display letting you know in real time when the next bus will be there. My first thought was to reverse engineer its RF signal but something easier then came to mind.
In the very same bus stations, a small QR code brings you to a web page displaying the very same "minutes before bus arrival"... HTML parsing therefore made more sense given that I was fairly busy with other projects.

What Did I Have In Stock?

layettes.JPG
Every time I design a platform for a client or for my own projects, I order extra components (who doesn't, right?).
To display the number of minutes before bus arrival, I used a big 2.3" common anode 7 segment display:

7segmentdisp.JPG
As you can guess, it doesn't use 2.5V per segment... but something around 7.5V.
I therefore needed a voltage step-up: the LM4510 was therefore found in my inventory.
Next thing: the power source. I really didn't want to have a power supply plugged 24/7 for this platform.
What I did have laying around was a ~5600mAh battery from a USB power bank whose charging IC had burned:

burnt_charger.jpg
Yes, cheap USB power banks from China are a big no-no.

esp8266.jpg
Finally, as the brain of this platform, to connect to my Wifi and fetch the bus time of arrival web page: the very well known ESP8266 was chosen, running NodeMCU.

The Schematics

schematics.png
As previously mentioned, the LM4510 generates the 12V (I could have used a lower voltage) required by the common anode of the 7 segment display.
The PCA9624 then drives each segment cathode, and is connected to the ESP8266 through an I2C bus. Interestingly enough, it also has an enable input which can be used for blinking or even dimming.
A micro-USB connector (scrapped from a Mooltipass prototype) is used together with the MAX1551 to charge the Li-Ion battery. As the charge current is limited to 280mA... charging takes a while.
Finally, the LP38693 LDO generates the 3V3 required by the ESP8266.

The Board

board.JPG
You can easily guess what each component does from this picture. The PCB has the exact same size as the 7 segments display.
The two buttons are used to start the ESP8266 bootloader while the 4 pins connector brings out its UART.

platform.JPG
On the display side, you can notice that I 3D printed a "button extension". A simple press on it will wake up the ESP8266 from its deep sleep state to start displaying the time. As the LDO required a minimum 100uA load current, I actually had to add a load resistor!

The Source Code

As I said, designing the complete board and making its source code took me less than 10 hours.
NodeMCU definitely made things easy:

--bus_timer.lua-- Below: for each number (0->9) a boolean array specifying if we should set a non zero value into the pwm register, following the PCA9624 pwm register order
number_to_reg_val = {{1,1,1,0,1,0,1,1},{1,0,0,0,0,0,0,1},{0,1,1,1,0,0,1,1},{1,1,0,1,0,0,1,1},{1,0,0,1,1,0,0,1},{1,1,0,1,1,0,1,0},{1,1,1,1,1,0,1,0},{1,0,0,0,0,0,1,1},{1,1,1,1,1,0,1,1},{1,1,0,1,1,0,1,1}}
dash_reg_val = {0,0,0,1,0,0,0,0}
displayed_digits = {0, 0, 0, 0}
start_digit_reg_addr = 2
digit_address = 0x08
dot_shown = false
sda, scl = 1, 2
led_noe = 4
led_hv_en = 8
-- Below: time fetching logic
time_until_other_bus = 10
time_until_first_bus = 10
fetching_first_bus = true
fetching_done = true
displaying_first_bus = true
sleep_counter = 0

-- check for led controller
function check_led_controller()
	result = true
	
	i2c.start(0)											-- start i2c
	c = i2c.address(0, digit_address, i2c.TRANSMITTER)		-- see if something answers
	i2c.stop(0)												-- stop i2c
	if c == false then
		result = false
	end

	return result
end

-- write a register contents in a PCA
function write_reg_PCA(digit_addr, reg, val)
  i2c.start(0)
  i2c.address(0, digit_addr, i2c.TRANSMITTER)
  i2c.write(0, reg)
  i2c.write(0, val)
  i2c.stop(0)
end

-- update all segment registers inside a pca
function update_segment_registers(digit_addr, reg_bool_array, pwm_value)
	i2c.start(0)
	i2c.address(0, digit_addr, i2c.TRANSMITTER)
	i2c.write(0, start_digit_reg_addr + 128)
	for i=1,8 do
		if reg_bool_array[i] == 0 then
			i2c.write(0, 0)
		else
			i2c.write(0, pwm_value)
		end
	end
  i2c.stop(0)
end

-- display the number
function display_number(number)
	if number < 10 then
		-- display dot or not
		if dot_shown == true then
			number_to_reg_val[number+1][6] = 1
		end
		update_segment_registers(digit_address, number_to_reg_val[number+1], 0x10)
		number_to_reg_val[number+1][6] = 0
	else
		update_segment_registers(digit_address, dash_reg_val, 0x10)
	end
end

-- IO INIT
gpio.mode(led_noe, gpio.OUTPUT)						-- Led output enable GPIO2
gpio.write(led_noe, gpio.LOW)						-- Enable output
gpio.mode(led_hv_en, gpio.OUTPUT)					-- Led high voltage enable GPIO15
gpio.write(led_hv_en, gpio.HIGH)					-- Enable output
i2c.setup(0, sda, scl, i2c.SLOW)					-- init i2c

-- CHECK LED CONTROLLER
res = check_led_controller()
if res == true then
	print("LED controller here!")
else
	print("LED controller not found !!")
end

-- INIT LED CONTROLLER SETTINGS
-- Register auto-increment, normal mode, responds to all call
write_reg_PCA(digit_address, 0x00, 0x81)
-- LED driver 0->7 individual brightness and group dimming/blinking can be controlled through its PWMx register and the GRPPWM registers.
write_reg_PCA(digit_address, 0x0C, 0xFF)
write_reg_PCA(digit_address, 0x0D, 0xFF)

-- TIME FETCHING, every 5secs
tmr.alarm(0,5000,tmr.ALARM_AUTO,
	function()
		sleep_counter = sleep_counter + 1
		if sleep_counter == 240 then							-- 20 minutes of activity
			write_reg_PCA(digit_address, 0x00, 0x91)			-- pca9624 sleep
			write_reg_PCA(digit_address, 0x00, 0x91)			-- pca9624 sleep
			gpio.write(led_hv_en, gpio.LOW)						-- Disable output
			gpio.write(led_noe, gpio.HIGH)						-- Disable output
			node.dsleep(0)
			sleep_counter = 0
		end
		if fetching_done == true then
			if fetching_first_bus == false then
				http.get("VERYLONGURLHERE", nil, function(code, data)
						if (code < 0) then
							time_until_first_bus = 10
							print("HTTP request failed")
						else
							matches = string.match(data, '"time">(%d*)\'')
							if matches ~= nil then
								time_until_first_bus = tonumber(matches)
								print(string.format("Next bus #1 in %d minutes", time_until_first_bus))
							else
								time_until_first_bus = 10
							end
						end
						fetching_done = true
					end)
				fetching_first_bus = true
			else
				http.get("ANOTHERLONGURLTHERE", nil, function(code, data)
						if (code < 0) then
							time_until_other_bus = 10
							print("HTTP request failed")
						else
							matches = string.match(data, '"time">(%d*)\'')
							if matches ~= nil then
								time_until_other_bus = tonumber(matches)
								print(string.format("Next bus #2 in %d minutes", time_until_other_bus))
							else
								time_until_other_bus = 10
							end
						end
						fetching_done = true
					end)			
				fetching_first_bus = false
			end
			fetching_done = false
		end
	end)	

-- TIME DISPLAY, every 2 secs	
tmr.alarm(1,2000,tmr.ALARM_AUTO,
	function()
		if displaying_first_bus == false then
			display_number(time_until_first_bus)
			displaying_first_bus = true
		else
			display_number(time_until_other_bus)
			displaying_first_bus = false
		end
	end)

A regular expression was used to extract the number of minutes from the web page, and if you look into the source code above you'll notice that I'm displaying 2 different times of arrival as my wife can use 2 buses to go to the train station.

The Source Files

This project was quickly done so I do apologize for the lack of documentation!
The kicad source files can be downloaded here.
Cheers!

Comments

1. On Friday, August 4 2017, 09:02 by Alexander

Thank You, Mathieu, for very useful and valuable project!
Is possible to share the link of the bus stations web page?

2. On Friday, August 25 2017, 12:20 by limpkin

@Alexander : Except if you live in my city... i doubt it'll really be useful to you!

3. On Tuesday, October 24 2017, 11:33 by ucasano

Thank you for sharing :-) Where did you buy the stock containers you showed in first photo??

4. On Tuesday, October 24 2017, 17:37 by limpkin

@ucasano : thanks! found them on ebay

They posted on the same topic

Trackback URL : https://www.limpkin.fr/index.php?trackback/212

This post's comments feed