Skip to content

Instantly share code, notes, and snippets.

@simoneb simoneb/
Last active Aug 29, 2015

What would you like to do? dashing widget


Dashing widget to show details about your Italian Wind mobile contracts. It supports both prepaid SIM cards and contracts and can display:

  • credit
  • voice traffic
  • text traffic
  • data traffic
  • expiries

This widget relies on the same HTTP API used by the mobile Wind apps, which is undocumented and can break at any time

Wind's HTTP API only serves private customers, it cannot be used for business contracts


  • Add json to your Gemfile and run bundle install
  • Create a file named mywind.yml in the config folder and set your credentials and mobile contracts:
- user: "your user"
  password: "your password"
    some_widget_id: "a mobile number"
    another_widget_id: "another mobile number"
  • Download gauge.js into the assets/javascripts folder

  • Download the file mywind.rb below into the lib folder

  • Then either:

    1. Download the coffee/scss/html files below into a widget folder named mywind
    2. Download the mywind-job.rb file below into the jobs folder

    Or simply: dashing install 9041024

    This method also downloads mywind.rb into the jobs folder, that is OK although unnecessary and can be deleted from there

  • Finally create a dashboard and use the same widgets ids set in the configuration files as data-id attributes and set data-view="Mywind":

<li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
  <div data-id="some_widget_id" data-view="Mywind" data-title="My Internet Key"></div>


A mobile prepaid SIM with a data plan requires two columns' space (data-sizex="2").

Mobile prepaid SIM

A contract mobile broadband SIM fits into one column (data-sizex="1").

Mobile broadband contract SIM

config_file = File.dirname(File.expand_path(__FILE__)) + '/../config/mywind.yml'
config = YAML::load(
accounts = do |account|
{ mywind:['user'], account['password']), contracts: account['contracts'] }
SCHEDULER.every '5m', :first_in => 0 do |job|
accounts.each do |account|
data = account[:mywind].data
account[:contracts].each do |widget_id, msisdn|
msisdn_data = data[msisdn]
result = {
dataTraffic: {
volTot: Integer(msisdn_data['shaping-details']['vol-tot']),
comThreshold: Integer(msisdn_data['shaping-details']['com-threshold']),
periodEndDate: DateTime.parse(msisdn_data['shaping-details']['period-end-date'])
paymentType = msisdn_data['line-tariffplan']['payment-type']
# prepaid
if paymentType == 'PRE'
result[:creditBalance] = msisdn_data['credit-balance-value'].gsub(',', '.').to_f
/(?<residuoMin>\d+) minut[oi] (?<residuoSec>\d+) second[oi]/ =~ msisdn_data['line-options'][0]['BonusInfoDetails']['residuo-min']
/(?<consumoMin>\d+) minut[oi] (?<consumoSec>\d+) second[oi]/ =~ msisdn_data['line-options'][0]['BonusInfoDetails']['consumo-min']
result[:voiceTraffic] = {
residuoMin: Integer(residuoMin),
residuoSec: Integer(residuoSec),
consumoMin: Integer(consumoMin),
consumoSec: Integer(consumoSec)
result[:voiceTraffic][:totMin] = result[:voiceTraffic][:residuoMin] +
result[:voiceTraffic][:consumoMin] +
(result[:voiceTraffic][:consumoSec] + result[:voiceTraffic][:residuoSec]) / 60
result[:textTraffic] = {
residuoSms: Integer(msisdn_data['line-options'][0]['BonusInfoDetails']['residuo-sms']),
consumoSms: Integer(msisdn_data['line-options'][0]['BonusInfoDetails']['consumo-sms'])
result[:textTraffic][:totSms] = result[:textTraffic][:residuoSms] + result[:textTraffic][:consumoSms]
result[:voiceTraffic][:expiry] = result[:textTraffic][:expiry] = DateTime.parse(msisdn_data['line-options'][0]['BonusInfoDetails']['expiry'])
# contract
elsif paymentType == 'POST'
send_event(widget_id, result)
class Dashing.Mywind extends Dashing.Widget
@accessor 'hasVoiceTraffic', ->
true if @get('voiceTraffic')
@accessor 'hasTextTraffic', ->
true if @get('textTraffic')
@accessor 'hasDataTraffic', ->
true if @get('dataTraffic')
@accessor 'voiceTrafficLabel', ->
return unless @get('voiceTraffic')
"#{@get('voiceTraffic').consumoMin} / #{@get('voiceTraffic').totMin} min"
@accessor 'textTrafficLabel', ->
return unless @get('textTraffic')
"#{@get('textTraffic').consumoSms} / #{@get('textTraffic').totSms} SMS"
@accessor 'dataTrafficLabel', ->
dataTraffic = @get('dataTraffic')
return unless dataTraffic
consumoGB = dataTraffic.volTot / 1024 / 1024 / 1024
totGB = dataTraffic.comThreshold / 1024 / 1024 / 1024
"#{consumoGB.toFixed(2)} / #{totGB.toFixed(2)} GB"
@accessor 'voiceTrafficExpiry', ->
new Date(@get('voiceTraffic').expiry).toLocaleDateString() if @get('voiceTraffic')
@accessor 'textTrafficExpiry', ->
new Date(@get('textTraffic').expiry).toLocaleDateString() if @get('textTraffic')
@accessor 'dataTrafficExpiry', ->
new Date(@get('dataTraffic').periodEndDate).toLocaleDateString() if @get('dataTraffic')
ready: ->
@opts =
lines: 12
angle: 0.06
lineWidth: 0.29
pointer: {
length: 0.6
strokeWidth: 0.046
color: '#000000'
limitMax: 'false'
colorStart: '#6FADCF'
colorStop: '#8FC0DA'
strokeColor: '#E0E0E0'
generateGradient: true
@isReady = true
onData: (data) ->
return unless @isReady
drawVoiceTraffic: (data) ->
return unless data
@voiceGauge = new Gauge($(@node).find('.voiceTraffic')[0]).setOptions(@opts) unless @voiceGauge
@voiceGauge.maxValue = data.totMin
drawTextTraffic: (data) ->
return unless data
@textGauge = new Gauge($(@node).find('.textTraffic')[0]).setOptions(@opts) unless @textGauge
@textGauge.maxValue = data.totSms
drawDataTraffic: (data) ->
return unless data
@dataGauge = new Gauge($(@node).find('.dataTraffic')[0]).setOptions(@opts) unless @dataGauge
@dataGauge.maxValue = data.comThreshold
<h1 class="title" data-bind="title"></h1>
<p data-showif="creditBalance">
Credito disponibile <span data-bind="creditBalance | append ' &euro;'"></span>
<ul data-showif="hasVoiceTraffic">
<li data-bind="voiceTrafficLabel" class="counter"></li>
<canvas class="voiceTraffic"></canvas>
<li class="expiry">
Entro il <span data-bind="voiceTrafficExpiry"></span>
<ul data-showif="hasTextTraffic">
<li data-bind="textTrafficLabel" class="counter"></li>
<canvas class="textTraffic"></canvas>
<li class="expiry">
Entro il <span data-bind="textTrafficExpiry"></span>
<ul data-showif="hasDataTraffic">
<li data-bind="dataTrafficLabel" class="counter"></li>
<canvas class="dataTraffic"></canvas>
<li class="expiry">
Entro il <span data-bind="dataTrafficExpiry"></span>
<p class="more-info" data-bind="moreinfo"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>
require 'net/http'
require 'uri'
require 'rubygems'
require 'json'
require 'date'
require 'securerandom'
class SessionExpired < StandardError; end
class SessionInvalid < StandardError; end
class UnknownLoginError < StandardError
def initialize(status, reason)
@status = status
@reason = reason
class UnknownLineError < StandardError
attr_reader :status, :reason
def initialize(status, reason)
@status = status
@reason = reason
class MyWind
LOGIN_URI = URI.parse('')
LINES_URI = URI.parse('')
def initialize(user, pass)
@login_req =
@login_req.set_form_data('username' => user, 'password' => pass)
@login_req['Accept'] = 'application/json'
@line_req =
@line_req['Accept'] = 'application/json'
def data
http = Net::HTTP::new(, LOGIN_URI.port)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.use_ssl = true
unless @session_id
do_login http
request_lines http
rescue SessionExpired => e
rescue SessionInvalid => e
puts e
do_login http
request_lines http
def do_login(http)
puts 'mywind: logging in'
login_res = http.request @login_req
login_data = JSON.parse login_res.body
status, reason = read_response login_data
raise, reason) if status != '0'
puts 'mywind: login successful'
@customer_code = login_data['customer-code']
@session_id = login_data['login']['session']['id']
@lines = login_data['login']['lines'].map do |line|
{msisdn: line['msisdn'], contractCode: line['contract-code']}
def request_lines(http)
puts 'mywind: requesting lines'
Hash[ do |line|
@line_req.set_form_data('sessionid' => @session_id,
'msisdn' => line[:msisdn],
'contract-code' => line[:contractCode],
'customer-code' => @customer_code)
line_res = http.request @line_req
line_data = JSON.parse line_res.body
status, reason = read_response line_data
raise if status == '10'
raise if status == '11'
raise, reason) if status != '0'
puts "mywind: #{line[:msisdn]} requested successfully"
[line[:msisdn], line_data]
def messup_session
@session_id = SecureRandom.hex
def read_response(json)
return json['response']['status'], json['response']['reason']
private :do_login, :request_lines, :messup_session, :read_response
$background-color: #47bbb3;
$title-color: rgba(255, 255, 255, 0.7);
$moreinfo-color: rgba(255, 255, 255, 0.7);
$gauge-font-size: .7em;
.widget-mywind {
background-color: $background-color;
.title {
color: $title-color;
.more-info {
color: $moreinfo-color;
.updated-at {
color: rgba(0, 0, 0, 0.3);
canvas {
max-width: 180px;
ul {
display: inline-block;
margin-top: 10px;
.counter {
font-size: $gauge-font-size;
.expiry {
color: $moreinfo-color;
font-size: $gauge-font-size;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.