Skip to content

Instantly share code, notes, and snippets.

@simoneb simoneb/README.md
Last active Aug 29, 2015

Embed
What would you like to do?
MyWind.it dashing widget

Description

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

Installation

  • 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 wind.it user"
  password: "your wind.it password"
  contracts:
    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>
</li>

Screenshots

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(File.open(config_file))
accounts = config.map do |account|
{ mywind: MyWind.new(account['user'], account['password']), contracts: account['contracts'] }
end
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'
end
send_event(widget_id, result)
end
end
end
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
@drawVoiceTraffic(@get('voiceTraffic'))
@drawTextTraffic(@get('textTraffic'))
@drawDataTraffic(@get('dataTraffic'))
onData: (data) ->
return unless @isReady
@drawVoiceTraffic(data.voiceTraffic)
@drawTextTraffic(data.textTraffic)
@drawDataTraffic(data.dataTraffic)
drawVoiceTraffic: (data) ->
return unless data
@voiceGauge = new Gauge($(@node).find('.voiceTraffic')[0]).setOptions(@opts) unless @voiceGauge
@voiceGauge.maxValue = data.totMin
@voiceGauge.set(data.consumoMin)
drawTextTraffic: (data) ->
return unless data
@textGauge = new Gauge($(@node).find('.textTraffic')[0]).setOptions(@opts) unless @textGauge
@textGauge.maxValue = data.totSms
@textGauge.set(data.consumoSms)
drawDataTraffic: (data) ->
return unless data
@dataGauge = new Gauge($(@node).find('.dataTraffic')[0]).setOptions(@opts) unless @dataGauge
@dataGauge.maxValue = data.comThreshold
@dataGauge.set(data.volTot)
<h1 class="title" data-bind="title"></h1>
<p data-showif="creditBalance">
Credito disponibile <span data-bind="creditBalance | append ' &euro;'"></span>
</p>
<ul data-showif="hasVoiceTraffic">
<li data-bind="voiceTrafficLabel" class="counter"></li>
<li>
<canvas class="voiceTraffic"></canvas>
</li>
<li class="expiry">
Entro il <span data-bind="voiceTrafficExpiry"></span>
</li>
</ul>
<ul data-showif="hasTextTraffic">
<li data-bind="textTrafficLabel" class="counter"></li>
<li>
<canvas class="textTraffic"></canvas>
</li>
<li class="expiry">
Entro il <span data-bind="textTrafficExpiry"></span>
</li>
</ul>
<ul data-showif="hasDataTraffic">
<li data-bind="dataTrafficLabel" class="counter"></li>
<li>
<canvas class="dataTraffic"></canvas>
</li>
<li class="expiry">
Entro il <span data-bind="dataTrafficExpiry"></span>
</li>
</ul>
<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
end
end
class UnknownLineError < StandardError
attr_reader :status, :reason
def initialize(status, reason)
@status = status
@reason = reason
end
end
class MyWind
LOGIN_URI = URI.parse('https://authserv.infostrada.it/155wide/auth/new/LoginUidPwd')
LINES_URI = URI.parse('https://authserv.infostrada.it/155wide/line/LineSummary')
def initialize(user, pass)
@login_req = Net::HTTP::Post.new(LOGIN_URI.path)
@login_req.set_form_data('username' => user, 'password' => pass)
@login_req['Accept'] = 'application/json'
@line_req = Net::HTTP::Post.new(LINES_URI.path)
@line_req['Accept'] = 'application/json'
end
def data
http = Net::HTTP::new(LOGIN_URI.host, LOGIN_URI.port)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.use_ssl = true
#http.set_debug_output($stdout)
unless @session_id
do_login http
end
begin
request_lines http
rescue SessionExpired => e
rescue SessionInvalid => e
puts e
do_login http
request_lines http
end
end
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 UnknownLoginError.new(status, 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']}
end
end
def request_lines(http)
puts 'mywind: requesting lines'
Hash[@lines.map 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 SessionInvalid.new if status == '10'
raise SessionExpired.new if status == '11'
raise UnknownLineError.new(status, reason) if status != '0'
puts "mywind: #{line[:msisdn]} requested successfully"
[line[:msisdn], line_data]
end]
end
def messup_session
@session_id = SecureRandom.hex
end
def read_response(json)
return json['response']['status'], json['response']['reason']
end
private :do_login, :request_lines, :messup_session, :read_response
end
$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.