mf60 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .rvmrc
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mf60a.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :test do
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
6
+ watch(%r{^test/.+_test\.rb$})
7
+ watch('test/test_helper.rb') { "test" }
8
+
9
+ end
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module MF60
2
+ VERSION = "0.1.0"
3
+ end
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