Skip to content

Instantly share code, notes, and snippets.

@jbfarez
Last active October 27, 2022 09:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbfarez/8fd422d5e4e3d6614c72 to your computer and use it in GitHub Desktop.
Save jbfarez/8fd422d5e4e3d6614c72 to your computer and use it in GitHub Desktop.
Dashing Paris Velib widget

Preview

Screenshot

Description

Retrieve the real-time status of Paris Velib bike stations (Bikes and stands availability).

Requirements

Usage

Add this to your erb dashboard file (You have to replace {STATION_ALIAS} by the alias of your choice, this alias will be reused) :

<!-- Velib {STATION_ALIAS} station-->
<li data-row="1" data-col="1" data-sizex="4" data-sizey="3">
  <div data-id="velib-{STATION_ALIAS}" data-title="" data-view="Velib"></div>
</li>

You have to add this kind of block for each station you want to retrieve the status.

Settings

Job

You have to declare your API Key (In my case, I prefer to use an environment variable) :

API_KEY = ENV["VELIB_API_KEY"]

You can retrive multiple stations datas by adding them to the following array the key should be the STATION_ALIAS and the value should be the ID of the station(s) :

stations = {
  'victoire' => '9111', # Replace this by the station you want to retrieve
}

Widget

  • Just copy velib.coffee, velib.html and velib.scss to {DASHING_PATH}/widgets/velib/
  • Put the bike.svg to {DASHING_PATH}/assets/images/

NB :

Thanks to @stephenyeargin for the inspiration with his B-Cycle widget.

API Documentation here.

Display the source blob
Display the rendered blob
Raw
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
height="725"
width="725"
opacity="0.1">
<path
style="fill:#ffffff;fill-rule:evenodd;stroke:#FFFFFF;stroke-width:2.5px"
id="OuterCircle"
d="M -101.56634,275.21499 C -101.56634,468.51465 -258.26668,625.21499 -451.56634,625.21499 C -644.866,625.21499 -801.56634,468.51465 -801.56634,275.21499 C -801.56634,81.915331 -644.866,-74.785013 -451.56634,-74.785013 C -258.26668,-74.785013 -101.56634,81.915331 -101.56634,275.21499 L -101.56634,275.21499 z "
inkscape:export-xdpi="2.5599999"
inkscape:export-ydpi="2.5599999" />
<g
id="g3138"
transform="translate(-90.847666,-19.594595)"
inkscape:export-xdpi="4.2362185"
inkscape:export-ydpi="4.2362185">
<circle
transform="matrix(0.612193,0,0,0.612193,156.40859,178.29391)"
r="161"
cy="409"
cx="215"
style="fill:none;fill-opacity:1;stroke:#FFFFFF;stroke-width:40px;stroke-opacity:1"
id="FrontWheel"
sodipodi:cx="215"
sodipodi:cy="409"
sodipodi:rx="161"
sodipodi:ry="161" />
<circle
transform="matrix(0.612193,0,0,0.612193,156.40859,178.29391)"
r="161"
cy="406"
cx="790"
style="fill:none;fill-opacity:1;stroke:#FFFFFF;stroke-width:40px;stroke-opacity:1"
id="RearWheel"
sodipodi:cx="790"
sodipodi:cy="406"
sodipodi:rx="161"
sodipodi:ry="161" />
<path
style="fill:#FFFFFF;fill-opacity:1;fill-rule:evenodd;stroke:#FFFFFF;stroke-width:0.61219299;stroke-opacity:1"
id="Frame"
d="M 419.33591,187.76277 C 338.17965,188.55377 351.94299,179.37821 308.27997,319.70103 C 272.71002,431.81886 276.58445,419.592 276.39842,427.45646 C 276.26805,433.56628 281.20005,438.4105 287.39765,438.82515 C 300.28652,437.52193 294.86517,440.2191 339.55071,301.45538 C 502.83002,473.69932 441.57171,445.76834 529.44266,449.2663 C 644.92819,448.15891 654.31469,455.27788 655.68575,438.35055 C 655.52755,432.60263 652.83816,429.17498 573.58029,252.78271 L 581.17385,231.90039 L 607.27675,231.42579 L 606.32755,209.59428 L 509.03495,209.59428 C 500.43945,221.35377 512.51534,221.30103 556.02016,230.9512 L 548.42659,252.3081 L 354.31598,252.78271 C 367.39379,207.11583 360.01116,209.96341 418.86132,210.06888 L 419.33591,187.76277 z M 348.70707,276.18977 L 538.64814,275.85299 L 483.75383,423.36041 L 348.70707,276.18977 z M 561.54883,284.94591 L 507.66484,426.39139 L 625.53607,426.72816 L 561.54883,284.94591 z " />
</g>
</svg>
class Dashing.Velib extends Dashing.Widget
@accessor 'stateClass', ->
if @get('state') == "OPEN"
"station-state-open"
else if @get('state') == "CLOSED"
"station-state-closed"
else
"station-state-unknown"
constructor: ->
super
ready: ->
$(@node).addClass(@get('stateClass'))
onData: (data) ->
$(@node).addClass(@get('stateClass'))
<h1 class="title" data-bind="station"></h1>
<h3>
<ul>
<li>
<span data-bind="bikes_available"></span> Bikes</br>
</li>
<li>
<span data-bind="stands_available"></span> Stands
</li>
</ul>
</h3>
<p class="more-info">State : <span data-bind="state"></span></p>
<p class="more-info" data-bind="moreinfo | raw"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>
#!/usr/bin/env ruby
require 'httparty'
require 'json'
## Velib API key to get on JC Decaux dev website
## more infos : https://developer.jcdecaux.com/
## --
API_KEY = ENV["VELIB_API_KEY"]
## Stations to retrieve
## --
stations = {
'victoire' => '9111',
}
## Job itself
## --
SCHEDULER.every '2m', :first_in => 0 do |job|
# Define keys if not defined
API_KEY = '' unless defined?(API_KEY)
stations.each do |station,station_id|
begin
url = "https://api.jcdecaux.com/vls/v1/stations/#{station_id}?contract=Paris&apiKey=#{API_KEY}"
response = HTTParty.get(url)
datas = JSON.parse(response.body)
station_name = datas['name'].partition('- ').last
status = datas['status']
total_stands = datas['bike_stands']
available_stands = datas['available_bike_stands']
available_bikes = datas['available_bikes']
if not defined?(send_event)
puts "#------------------------------------------------------------#"
puts "Station name : #{station_name}"
puts "Status : #{status}"
puts "Total stands : #{total_stands}"
puts "Available stands : #{available_stands}"
puts "Available bikes : #{available_bikes}"
puts "#------------------------------------------------------------#"
else
title = 'velib-'+station.to_s
send_event(title, station: station_name, state: status, bikes_available: available_bikes, stands_available: available_stands)
end
rescue
puts response.body
puts "Something went wrong ... Could not fetch the informations."
end
end
end
.widget-velib {
background-image: url("/assets/bike.svg");
background-repeat: no-repeat;
background-position: center center;
background-size: 400px 400px;
background-color: #000000;
.updated-at {
color: rgba(0, 0, 0, 0.3);
}
}
.station-state-open {
background-color: #b0c964;
}
.station-state-closed {
background-color: #d04035;
}
.station-state-unknown {
background-color: #bfbdbe;
}
@JCluzet
Copy link

JCluzet commented Oct 26, 2022

Your widget don't work, unauthorized is write on launch.... :(

@jbfarez
Copy link
Author

jbfarez commented Oct 27, 2022

Hey @JCluzet, sorry to hear that but It has been written almost 8 years ago... It's not maintained anymore.
Moreover, if I remember correctly, the provider is not JCDecaux anymore.
You might want to adapt it to use the OpenData API ;)

Happy hacking!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment