mf60 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/README.md +42 -0
- data/Rakefile +13 -0
- data/bin/mf60 +72 -0
- data/lib/mf60.rb +113 -0
- data/lib/mf60/helpers.rb +23 -0
- data/lib/mf60/version.rb +3 -0
- data/mf60.gemspec +28 -0
- data/test/mf60_test.rb +34 -0
- metadata +106 -0
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# MF60 #
|
2
|
+
|
3
|
+
The MF60 is a portable, personal WIFI hotspot that is sold worldwide in countries like Australia and Switzerland (where I live). Apparently, over a million of these devices were sold in Australia alone last year.
|
4
|
+
|
5
|
+
Like most hotspots, it comes with a pretty pitiful web interface and not much in the way of an API. I commute on the train frequently, and I sometimes find I need to reset the box. I was curious to see if I could somehow automate that a little bit. For instance, I wanted to be able to quickly disconnect/reconnect from the command-line, or maybe gather statistics. So I wrote this library.
|
6
|
+
|
7
|
+
This Gem provides a Ruby library that lets you connect to the device and control it:
|
8
|
+
|
9
|
+
> require 'MF60'
|
10
|
+
> mf60 = MF60::Client.new('admin', 'secret')
|
11
|
+
> mf60.reset
|
12
|
+
=> true
|
13
|
+
|
14
|
+
You can also get stats:
|
15
|
+
|
16
|
+
> mf60.stats
|
17
|
+
=> {:current_trans=>1133211, :current_recv=>2884959, :connect_time=>"29:33", :total_recv=>222000830}
|
18
|
+
|
19
|
+
There's a command-line utility installed as well with the gem that makes this easy to do:
|
20
|
+
|
21
|
+
$ mf60
|
22
|
+
Tasks:
|
23
|
+
mf60 connect # Connect to the cellular network
|
24
|
+
mf60 disconnect # Disconnect from the cellular network
|
25
|
+
mf60 help [TASK] # Describe available tasks or one specific task
|
26
|
+
mf60 reset # Reset the WAN connection
|
27
|
+
mf60 stats # Get statistics and usage info
|
28
|
+
mf60 status # Display connection status and signal strength
|
29
|
+
|
30
|
+
Tracking signal strength can be interesting as well:
|
31
|
+
|
32
|
+
$ mf60 status
|
33
|
+
MF60 Current Status
|
34
|
+
-------------------
|
35
|
+
Provider : Swisscom
|
36
|
+
Network Type : UMTS
|
37
|
+
Device State : modem_init_complete
|
38
|
+
rscp/ecio : 176/16
|
39
|
+
rssi : 11
|
40
|
+
signal : ▁ ▂ ▃......
|
41
|
+
|
42
|
+
The project is built with [HTTParty](https://github.com/jnunemaker/httparty) and [Thor](https://github.com/wycats/thor). Both libraries are fun and awesome to use.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/clean'
|
6
|
+
|
7
|
+
task :default => [:test]
|
8
|
+
|
9
|
+
desc 'Run tests (default)'
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.test_files = Dir.glob 'test/*_test.rb'
|
12
|
+
t.ruby_opts = ['-Ilib','-rubygems']
|
13
|
+
end
|
data/bin/mf60
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
require 'rubygems'
|
4
|
+
require 'mf60'
|
5
|
+
require 'httparty'
|
6
|
+
require 'thor'
|
7
|
+
|
8
|
+
class MF60Util < Thor
|
9
|
+
# Usage: #{app} <command>
|
10
|
+
# Commands:
|
11
|
+
# status current device info
|
12
|
+
# stats show connection statistics
|
13
|
+
# connect connect WAN (3G/2G)
|
14
|
+
# disconnect disconnect WAN
|
15
|
+
# reset reset (disconnect, then connect)
|
16
|
+
|
17
|
+
USER ='admin'
|
18
|
+
PASSWORD = ENV['MF60PW'] || raise("Error: No password set. Environment var MF60PW should contain the device password.")
|
19
|
+
|
20
|
+
desc "reset", "Reset the WAN connection"
|
21
|
+
def reset
|
22
|
+
mf60 = MF60::Client.new(USER, PASSWORD)
|
23
|
+
mf60.reset
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "connect", "Connect to the cellular network"
|
27
|
+
def connect
|
28
|
+
mf60 = MF60::Client.new(USER, PASSWORD)
|
29
|
+
mf60.connect
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "disconnect", "Disconnect from the cellular network"
|
33
|
+
def disconnect
|
34
|
+
mf60 = MF60::Client.new(USER, PASSWORD)
|
35
|
+
mf60.disconnect
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "status", "Display connection status and signal strength"
|
39
|
+
def status
|
40
|
+
mf60 = MF60::Client.new(USER, PASSWORD)
|
41
|
+
st = mf60.status
|
42
|
+
ticks = '▁ ▂ ▃ ▅ ▆ ▇'
|
43
|
+
top = 30 # rssi level for "full bars"
|
44
|
+
numbars = ([st[:rssi].to_i, top].min/top.to_f)*ticks.length.to_i
|
45
|
+
bars = ticks[0..numbars]+'.'*(ticks.length-numbars)
|
46
|
+
puts 'MF60 Current Status'
|
47
|
+
puts '-------------------'
|
48
|
+
puts "Provider : #{st[:provider]}"
|
49
|
+
puts "Network Type : #{st[:network_type_var]}"
|
50
|
+
puts "Device State : #{st[:cardstate]}"
|
51
|
+
puts "Network Mode : #{st[:current_network_mode]}" if st[:current_network_mode] =~ /.+/
|
52
|
+
puts "rscp/ecio : #{st[:rscp]}/#{st[:ecio]}"
|
53
|
+
puts "rssi : #{st[:rssi].to_i}"
|
54
|
+
puts "signal : #{bars}"
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "stats", "Get statistics and usage info"
|
58
|
+
def stats
|
59
|
+
mf60 = MF60::Client.new(USER, PASSWORD)
|
60
|
+
info = mf60.stats
|
61
|
+
puts 'MF60 Current Statistics'
|
62
|
+
puts '-----------------------'
|
63
|
+
puts "Connect Time : #{info[:connect_time]}"
|
64
|
+
puts "Transmitted : #{MF60::Helpers.bytes_formatted(info[:current_trans])}"
|
65
|
+
puts "Received : #{MF60::Helpers.bytes_formatted(info[:current_recv])}"
|
66
|
+
puts "Total Recv : #{MF60::Helpers.bytes_formatted(info[:total_recv])}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
MF60Util.start
|
71
|
+
|
72
|
+
#vim: set ft=ruby
|
data/lib/mf60.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'httparty'
|
3
|
+
require 'mf60/version'
|
4
|
+
require 'mf60/helpers'
|
5
|
+
|
6
|
+
# The MF60 requires some kind of "luck number" with each post, but it does
|
7
|
+
# not seem to make mich difference what it is. The cookie is required to
|
8
|
+
# maintain the session after login.
|
9
|
+
|
10
|
+
module MF60
|
11
|
+
class Client
|
12
|
+
DEVICE_URL = ENV['MF60URL'] || "http://swisscom-mf60.home/"
|
13
|
+
|
14
|
+
include HTTParty
|
15
|
+
format :html
|
16
|
+
|
17
|
+
def initialize(user, pass)
|
18
|
+
@lucknum = 123456
|
19
|
+
response = self.class.post(
|
20
|
+
DEVICE_URL + '/goform/goform_process',
|
21
|
+
:body => {
|
22
|
+
'user' => user,
|
23
|
+
'psw' => pass,
|
24
|
+
'goformId'=>'LOGIN',
|
25
|
+
'lucknum'=>@lucknum
|
26
|
+
}
|
27
|
+
)
|
28
|
+
@cookie = response.headers['set-cookie']
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(path)
|
32
|
+
response = self.class.get(DEVICE_URL + path, :headers => {'Cookie' => @cookie})
|
33
|
+
end
|
34
|
+
|
35
|
+
def connect
|
36
|
+
net_connect(true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def disconnect
|
40
|
+
net_connect(false)
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset
|
44
|
+
self.disconnect
|
45
|
+
sleep 5
|
46
|
+
self.connect
|
47
|
+
end
|
48
|
+
|
49
|
+
def stats
|
50
|
+
response = self.get('/adm/statistics.asp')
|
51
|
+
info = response.match(/var realtime_statistics = '([\d\,]+)'/)[1]
|
52
|
+
stats = info.split(',')
|
53
|
+
{
|
54
|
+
:current_trans => stats[2].to_i,
|
55
|
+
:current_recv => stats[3].to_i,
|
56
|
+
:connect_time => "%02d:%02d" % [stats[4].to_i/60, stats[4].to_i%60],
|
57
|
+
:total_recv => stats[5].to_i
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# 0 -113 dBm or less
|
63
|
+
# 1 -111 dBm
|
64
|
+
# 2...30 -109... -53 dBm
|
65
|
+
# 31 -51 dBm or greater "good"
|
66
|
+
#
|
67
|
+
# 99 not known or not detectable
|
68
|
+
#
|
69
|
+
# RSCP is the received signal code power from the CPICH channel and it is usually constant in a cell and it gives an indication of the level in the area.
|
70
|
+
# RSSI: is the received signal strength indicator and it is the total power with the noise.
|
71
|
+
# Ec/N0 : is the Received Signal Code Power equal to RSCP/RSSI or the code power over (noise+code power)
|
72
|
+
# So... rscp/rssi = ecno
|
73
|
+
#
|
74
|
+
# rscp
|
75
|
+
# ---- = ecio | rscp = ecio * rssi | rssi = rscp/ecio
|
76
|
+
# rssi
|
77
|
+
|
78
|
+
def status
|
79
|
+
response = self.get('/air_network/wireless_info.asp')
|
80
|
+
vars = %w(provider network_type_var cardstate current_network_mode rscp ecio)
|
81
|
+
status = {}
|
82
|
+
vars.each do |var_name|
|
83
|
+
status[var_name.to_sym] = grab_var(response, var_name)
|
84
|
+
end
|
85
|
+
status[:rssi] = (status[:rscp].to_f/status[:ecio].to_f)
|
86
|
+
status
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# mode: 'connect' or 'disconnect'
|
92
|
+
def net_connect(active)
|
93
|
+
body = {
|
94
|
+
'goformId'=>'NET_CONNECT',
|
95
|
+
'lucknum_NET_CONNECT'=>@lucknum
|
96
|
+
}
|
97
|
+
if active
|
98
|
+
body['dial_mode'] = 'auto_dial'
|
99
|
+
else
|
100
|
+
body['dial_mode'] = 'manual_dial'
|
101
|
+
body['action'] = 'disconnect'
|
102
|
+
end
|
103
|
+
response = self.class.post(DEVICE_URL + '/goform/goform_process', :body => body)
|
104
|
+
response.success?
|
105
|
+
end
|
106
|
+
|
107
|
+
def grab_var(response, var_name)
|
108
|
+
part = response.match(/var\s+#{var_name}\s+=\s+'([^']+)'/)
|
109
|
+
part ? part[1] : ''
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
data/lib/mf60/helpers.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module MF60
|
2
|
+
# format bytes as 12B, 124K, 1.2M, 24G etc
|
3
|
+
class Helpers
|
4
|
+
def self.bytes_formatted(bytes)
|
5
|
+
return '-' if bytes <= 0
|
6
|
+
units = ['B','K','M','G','T']
|
7
|
+
t = 0
|
8
|
+
while bytes >= 1024**(t+1)
|
9
|
+
t+=1
|
10
|
+
end
|
11
|
+
n = t>0 ? (bytes/(1024.0**t)) : bytes
|
12
|
+
n = n.floor if (n-n.floor)<0.1
|
13
|
+
n = n.ceil if (n.ceil-n)<0.1
|
14
|
+
if n==1024
|
15
|
+
n = n/1024
|
16
|
+
t+=1
|
17
|
+
end
|
18
|
+
fn = (n-n.floor>0 and n<50) ? sprintf("%.1f", n) : n.ceil
|
19
|
+
"#{fn}#{units[t]}"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/mf60/version.rb
ADDED
data/mf60.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "mf60/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "mf60"
|
7
|
+
s.version = MF60::VERSION
|
8
|
+
s.authors = ["Jeremy Seitz"]
|
9
|
+
s.email = ["jeremy@somebox.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Ruby library and command-line tool for interfacing with the MF60 mobile internet hotspot device.}
|
12
|
+
s.description = %q{A library and command-line tool that talk to the MF60 via the admin web interface. This little battery-powered box is available from Swisscom in Switzerland (as well as mobile operators in other countries). With this gem you can get statistics, network info, signal strength, connect, disconnect and reset the device.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "mf60"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
|
23
|
+
s.add_runtime_dependency "httparty"
|
24
|
+
s.add_runtime_dependency "thor"
|
25
|
+
|
26
|
+
s.add_development_dependency "guard"
|
27
|
+
s.add_development_dependency "guard-test"
|
28
|
+
end
|
data/test/mf60_test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'mf60'
|
3
|
+
|
4
|
+
class MF60Test < Test::Unit::TestCase
|
5
|
+
def test_bytes_formatted
|
6
|
+
{
|
7
|
+
'-' => 0,
|
8
|
+
'1B' => 1,
|
9
|
+
'100B' => 100,
|
10
|
+
'589B' => 589,
|
11
|
+
'1K' => 1024,
|
12
|
+
'1.1K' => 1160,
|
13
|
+
'2K' => 2050,
|
14
|
+
'19.5K' => 20000,
|
15
|
+
'20K' => 20490,
|
16
|
+
'100K' => 100*2**10,
|
17
|
+
'102K' => 100*1024+1200,
|
18
|
+
'935K' => 957383,
|
19
|
+
'1M' => 1024*1024,
|
20
|
+
'1M' => 1024*1024-100,
|
21
|
+
'1M' => 1024*1024+100,
|
22
|
+
'1.1M' => 1024*1024+150*1024,
|
23
|
+
'6.5M' => 6809954,
|
24
|
+
'71M' => 74046302,
|
25
|
+
'242M' => 253012972,
|
26
|
+
'1.4G' => 1471936825,
|
27
|
+
'20G' => 1024**3*20,
|
28
|
+
'1.5T' => 1024**4*1.5,
|
29
|
+
}.each do |expected, num|
|
30
|
+
assert_equal expected, MF60::Helpers.bytes_formatted(num)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mf60
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jeremy Seitz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-05 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: &70333819869340 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70333819869340
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thor
|
27
|
+
requirement: &70333819868920 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70333819868920
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: guard
|
38
|
+
requirement: &70333819868500 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70333819868500
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: guard-test
|
49
|
+
requirement: &70333819868080 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70333819868080
|
58
|
+
description: A library and command-line tool that talk to the MF60 via the admin web
|
59
|
+
interface. This little battery-powered box is available from Swisscom in Switzerland
|
60
|
+
(as well as mobile operators in other countries). With this gem you can get statistics,
|
61
|
+
network info, signal strength, connect, disconnect and reset the device.
|
62
|
+
email:
|
63
|
+
- jeremy@somebox.com
|
64
|
+
executables:
|
65
|
+
- mf60
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- Guardfile
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- bin/mf60
|
75
|
+
- lib/mf60.rb
|
76
|
+
- lib/mf60/helpers.rb
|
77
|
+
- lib/mf60/version.rb
|
78
|
+
- mf60.gemspec
|
79
|
+
- test/mf60_test.rb
|
80
|
+
homepage: ''
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project: mf60
|
100
|
+
rubygems_version: 1.8.10
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Ruby library and command-line tool for interfacing with the MF60 mobile internet
|
104
|
+
hotspot device.
|
105
|
+
test_files:
|
106
|
+
- test/mf60_test.rb
|