Project In A Day: When Is My Next Bus?
By limpkin on Sunday, July 23 2017, 20:36 - My Projects - Permalink
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?
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:
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:
Yes, cheap USB power banks from China are a big no-no.
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
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
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.
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
Thank You, Mathieu, for very useful and valuable project!
Is possible to share the link of the bus stations web page?
Thank you for sharing :-) Where did you buy the stock containers you showed in first photo??
@ucasano : thanks! found them on ebay