gizwits_sac 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +26 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +46 -0
- data/LICENSE +21 -0
- data/README.md +102 -0
- data/README.zh_cn.md +103 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gizwits_sac.gemspec +37 -0
- data/lib/gizwits_sac/error.rb +19 -0
- data/lib/gizwits_sac/snoti_client.rb +191 -0
- data/lib/gizwits_sac/snoti_socket.rb +172 -0
- data/lib/gizwits_sac/version.rb +3 -0
- data/lib/gizwits_sac.rb +4 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc32b80766971cbb24e854e5903065995eabab11
|
4
|
+
data.tar.gz: 1cc278402bc41adb0007f5c2674e46c21b23cde6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6cd787363965c866202782d1e5ad3c248db317ec900727c7fbdf03328f7d4a5c673d6592ba70f46e5c8969708922396f57bb032ea67c0ec552d344038e4a5d3
|
7
|
+
data.tar.gz: 51fb4dcb555bce41b6117920508c7b7facf35d2afbc0b213a8f93a3eb5c8084ef805874230d05d35219bde166db78371f18e0a071033bd3e87a23aaa90c866d3
|
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
2
|
+
*.o
|
3
|
+
*.a
|
4
|
+
*.so
|
5
|
+
|
6
|
+
# Folders
|
7
|
+
_obj
|
8
|
+
_test
|
9
|
+
|
10
|
+
# Architecture specific extensions/prefixes
|
11
|
+
*.[568vq]
|
12
|
+
[568vq].out
|
13
|
+
|
14
|
+
*.cgo1.go
|
15
|
+
*.cgo2.c
|
16
|
+
_cgo_defun.c
|
17
|
+
_cgo_gotypes.go
|
18
|
+
_cgo_export.*
|
19
|
+
|
20
|
+
_testmain.go
|
21
|
+
|
22
|
+
*.exe
|
23
|
+
*.test
|
24
|
+
*.prof
|
25
|
+
|
26
|
+
/pkg/*
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gizwits_sac (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.1)
|
10
|
+
diff-lcs (1.3)
|
11
|
+
method_source (0.8.2)
|
12
|
+
pry (0.10.4)
|
13
|
+
coderay (~> 1.1.0)
|
14
|
+
method_source (~> 0.8.1)
|
15
|
+
slop (~> 3.4)
|
16
|
+
pry-nav (0.2.4)
|
17
|
+
pry (>= 0.9.10, < 0.11.0)
|
18
|
+
rake (10.5.0)
|
19
|
+
rspec (3.6.0)
|
20
|
+
rspec-core (~> 3.6.0)
|
21
|
+
rspec-expectations (~> 3.6.0)
|
22
|
+
rspec-mocks (~> 3.6.0)
|
23
|
+
rspec-core (3.6.0)
|
24
|
+
rspec-support (~> 3.6.0)
|
25
|
+
rspec-expectations (3.6.0)
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
+
rspec-support (~> 3.6.0)
|
28
|
+
rspec-mocks (3.6.0)
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
+
rspec-support (~> 3.6.0)
|
31
|
+
rspec-support (3.6.0)
|
32
|
+
slop (3.6.0)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
bundler (~> 1.13)
|
39
|
+
gizwits_sac!
|
40
|
+
pry
|
41
|
+
pry-nav
|
42
|
+
rake (~> 10.0)
|
43
|
+
rspec (~> 3.0)
|
44
|
+
|
45
|
+
BUNDLED WITH
|
46
|
+
1.13.6
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Abel
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# GizwitsSac-rb
|
2
|
+
|
3
|
+
GizwitsSac is short for Gizwits Snoti API Client, and GizwitsSac-rb just a Ruby version, which I hope it can help you more easily to connect to [Gizwits Snoti API](http://docs.gizwits.com/zh-cn/Cloud/NotificationAPI.html).
|
4
|
+
|
5
|
+
### [中文说明](https://github.com/AbelLai/gizwits_sac_rb/blob/master/README.zh_cn.md)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'gizwits_sac'
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
### 1. GizwitsSac::SnotiClient
|
17
|
+
Simply you can just use GizwitsSac::SnotiClient, and focus on the event via callback as the example below. (**GizwitsSac::SnotiClient will handle heartbeat for you in ervery seconds you set.**)
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require "gizwits_sac"
|
21
|
+
|
22
|
+
event_push_handler = Proc.new do |event_push_data|
|
23
|
+
# Here it just care the event *device_status_raw*.
|
24
|
+
if event_push_data["event_type"] == "device_status_raw"
|
25
|
+
puts "event_push_data =====> #{event_push_data}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
remote_ctrl_handler = Proc.new do
|
29
|
+
get_your_remote_ctrl_req
|
30
|
+
end
|
31
|
+
remote_ctrl_res_handler = Proc.new do |r_ctrl_res_data|
|
32
|
+
puts "r_ctrl_res_data =====> #{r_ctrl_res_data}"
|
33
|
+
end
|
34
|
+
|
35
|
+
client = GizwitsSac::SnotiClient.new({
|
36
|
+
event_push: event_push_handler,
|
37
|
+
remote_ctrl: remote_ctrl_handler,
|
38
|
+
remote_ctrl_res: remote_ctrl_res_handler,
|
39
|
+
heartbeat_interval: 60, # default value is 5
|
40
|
+
retry_count: 10, # default value is 5
|
41
|
+
logger: Logger.new(STDOUT),
|
42
|
+
host: "snoti.gizwits.com",
|
43
|
+
port: "2017",
|
44
|
+
connect_timeout: 3, # default value is 3 seconds
|
45
|
+
read_timeout: 3, # default value is 3 seconds
|
46
|
+
write_timeout: 3, # default value is 3 seconds
|
47
|
+
prefetch_count: 50, # default value is 50
|
48
|
+
auth_data: [
|
49
|
+
{
|
50
|
+
product_key: "your_product_key_here",
|
51
|
+
auth_id: "your_auth_id_here",
|
52
|
+
auth_secret: "your_auth_secret_here",
|
53
|
+
subkey: "your_subkey_here",
|
54
|
+
events: ['event_you_care_about', 'event_you_care_about', ...]
|
55
|
+
}
|
56
|
+
]
|
57
|
+
})
|
58
|
+
|
59
|
+
client.start
|
60
|
+
```
|
61
|
+
|
62
|
+
### 2. GizwitsSac::SnotiSocket
|
63
|
+
Another way, you can use GizwitsSac::SnotiSocket, and build a client as you like. Here is a very very simple example as below.
|
64
|
+
```ruby
|
65
|
+
require "gizwits_sac"
|
66
|
+
|
67
|
+
socket = GizwitsocketC::SnotiSocket.new({
|
68
|
+
host: "snoti.gizwits.com",
|
69
|
+
port: "2017",
|
70
|
+
connect_timeout: 3, # default value is 3 seconds
|
71
|
+
read_timeout: 3, # default value is 3 seconds
|
72
|
+
write_timeout: 3, # default value is 3 seconds
|
73
|
+
prefetch_count: 50, # default value is 50
|
74
|
+
auth_data: [
|
75
|
+
{
|
76
|
+
product_key: "your_product_key_here",
|
77
|
+
auth_id: "your_auth_id_here",
|
78
|
+
auth_secret: "your_auth_secret_here",
|
79
|
+
subkey: "your_subkey_here",
|
80
|
+
events: ['event_you_care_about', 'event_you_care_about', ...]
|
81
|
+
}
|
82
|
+
]
|
83
|
+
})
|
84
|
+
|
85
|
+
socket.connect
|
86
|
+
if socket.login_ok?
|
87
|
+
puts "login ok"
|
88
|
+
loop do
|
89
|
+
data = socket.read
|
90
|
+
puts data
|
91
|
+
sleep 2
|
92
|
+
end
|
93
|
+
else
|
94
|
+
puts "login failed"
|
95
|
+
end
|
96
|
+
```
|
97
|
+
## TODO: Unit Test
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
data/README.zh_cn.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# GizwitsSac-rb
|
2
|
+
|
3
|
+
GizwitsSac其实是Gizwits Snoti API Client的缩写,一个Ruby版本。
|
4
|
+
|
5
|
+
|
6
|
+
## 安装
|
7
|
+
|
8
|
+
往你的Gemfile加入以下这行:
|
9
|
+
```ruby
|
10
|
+
gem 'gizwits_sac'
|
11
|
+
```
|
12
|
+
|
13
|
+
## 用法
|
14
|
+
### 1. GizwitsSac::SnotiClient
|
15
|
+
使用GizwitsSac::SnotiClient,SnotiClient会帮你处理连接的部分以及心跳部分,你只用关心相关事件以及往连接塞你需要发送的远程控制命令。
|
16
|
+
|
17
|
+
例子:
|
18
|
+
```ruby
|
19
|
+
require "gizwits_sac"
|
20
|
+
|
21
|
+
event_push_handler = Proc.new do |event_push_data|
|
22
|
+
# Here it just care the event *device_status_raw*.
|
23
|
+
if event_push_data["event_type"] == "device_status_raw"
|
24
|
+
puts "event_push_data =====> #{event_push_data}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
remote_ctrl_handler = Proc.new do
|
28
|
+
get_your_remote_ctrl_req
|
29
|
+
end
|
30
|
+
remote_ctrl_res_handler = Proc.new do |r_ctrl_res_data|
|
31
|
+
puts "r_ctrl_res_data =====> #{r_ctrl_res_data}"
|
32
|
+
end
|
33
|
+
|
34
|
+
client = GizwitsSac::SnotiClient.new({
|
35
|
+
event_push: event_push_handler,
|
36
|
+
remote_ctrl: remote_ctrl_handler,
|
37
|
+
remote_ctrl_res: remote_ctrl_res_handler,
|
38
|
+
heartbeat_interval: 60, # default value is 5
|
39
|
+
retry_count: 10, # default value is 5
|
40
|
+
logger: Logger.new(STDOUT),
|
41
|
+
host: "snoti.gizwits.com",
|
42
|
+
port: "2017",
|
43
|
+
connect_timeout: 3, # default value is 3 seconds
|
44
|
+
read_timeout: 3, # default value is 3 seconds
|
45
|
+
write_timeout: 3, # default value is 3 seconds
|
46
|
+
prefetch_count: 50, # default value is 50
|
47
|
+
auth_data: [
|
48
|
+
{
|
49
|
+
product_key: "your_product_key_here",
|
50
|
+
auth_id: "your_auth_id_here",
|
51
|
+
auth_secret: "your_auth_secret_here",
|
52
|
+
subkey: "your_subkey_here",
|
53
|
+
events: ['event_you_care_about', 'event_you_care_about', ...]
|
54
|
+
}
|
55
|
+
]
|
56
|
+
})
|
57
|
+
|
58
|
+
client.start
|
59
|
+
```
|
60
|
+
|
61
|
+
### 2. GizwitsSac::SnotiSocket
|
62
|
+
GizwitsSac封装了一个SnotiSocket,你可以基于SnotiSocket实现自己的client.
|
63
|
+
|
64
|
+
例子:
|
65
|
+
```ruby
|
66
|
+
require "gizwits_sac"
|
67
|
+
|
68
|
+
socket = GizwitsocketC::SnotiSocket.new({
|
69
|
+
host: "snoti.gizwits.com",
|
70
|
+
port: "2017",
|
71
|
+
connect_timeout: 3, # default value is 3 seconds
|
72
|
+
read_timeout: 3, # default value is 3 seconds
|
73
|
+
write_timeout: 3, # default value is 3 seconds
|
74
|
+
prefetch_count: 50, # default value is 50
|
75
|
+
auth_data: [
|
76
|
+
{
|
77
|
+
product_key: "your_product_key_here",
|
78
|
+
auth_id: "your_auth_id_here",
|
79
|
+
auth_secret: "your_auth_secret_here",
|
80
|
+
subkey: "your_subkey_here",
|
81
|
+
events: ['event_you_care_about', 'event_you_care_about', ...]
|
82
|
+
}
|
83
|
+
]
|
84
|
+
})
|
85
|
+
|
86
|
+
socket.connect
|
87
|
+
if socket.login_ok?
|
88
|
+
puts "login ok"
|
89
|
+
loop do
|
90
|
+
data = socket.read
|
91
|
+
puts data
|
92
|
+
sleep 2
|
93
|
+
end
|
94
|
+
else
|
95
|
+
puts "login failed"
|
96
|
+
end
|
97
|
+
```
|
98
|
+
## TODO: Unit Test
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "gizwits_sac"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/gizwits_sac.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gizwits_sac/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gizwits_sac"
|
8
|
+
spec.version = GizwitsSac::VERSION
|
9
|
+
spec.authors = ["Abel Lai"]
|
10
|
+
spec.email = ["yilaqingfeng@gamil.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Gizwits Snoti API Client}
|
13
|
+
spec.description = %q{Gizwits Snoti API Client for Ruby.}
|
14
|
+
spec.homepage = "https://github.com/AbelLai/gizwits_sac_rb"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
20
|
+
else
|
21
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
22
|
+
"public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(test|spec|features)/})
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
spec.add_development_dependency "pry"
|
36
|
+
spec.add_development_dependency "pry-nav"
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
# GizwitsSac = Gizwits Snoti API Client
|
4
|
+
module GizwitsSac
|
5
|
+
class LoginError < SocketError
|
6
|
+
end
|
7
|
+
|
8
|
+
class ConnTimeoutError < SocketError
|
9
|
+
end
|
10
|
+
|
11
|
+
class ReadTimeoutError < SocketError
|
12
|
+
end
|
13
|
+
|
14
|
+
class WriteTimeoutError < SocketError
|
15
|
+
end
|
16
|
+
|
17
|
+
class ConnFailedError < SocketError
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
# Example for params when new a SnotiClient:
|
5
|
+
# options = {
|
6
|
+
# event_push: (Proc.new {|event_push_data| }),
|
7
|
+
# remote_ctrl: (Proc.new { return nil }),
|
8
|
+
# remote_ctrl_res: (Proc.new {|r_ctrl_data| }),
|
9
|
+
# error_res: (Proc.new {|err_data| }),
|
10
|
+
# heartbeat_interval: 60,
|
11
|
+
# logger: Logger.new("xxxx"),
|
12
|
+
# retry_count: 10,
|
13
|
+
# host: "xxx",
|
14
|
+
# port: "xxx",
|
15
|
+
# timeout: 3,
|
16
|
+
# read_timeout: 3,
|
17
|
+
# write_timeout: 3,
|
18
|
+
# prefetch_count: 50,
|
19
|
+
# auth_data: [
|
20
|
+
# {
|
21
|
+
# product_key: "xxxx",
|
22
|
+
# auth_id: "xxxx",
|
23
|
+
# auth_secret: "xxxx",
|
24
|
+
# subkey: "xxxx",
|
25
|
+
# events: ['xxx', 'xxx']
|
26
|
+
# }
|
27
|
+
# ]
|
28
|
+
# }
|
29
|
+
|
30
|
+
# GizwitsSac = Gizwits Snoti API Client
|
31
|
+
module GizwitsSac
|
32
|
+
class SnotiClient
|
33
|
+
def initialize(options)
|
34
|
+
@event_push = options.delete(:event_push) || (Proc.new {|event_push_data| })
|
35
|
+
@remote_ctrl = options.delete(:remote_ctrl) || (Proc.new { return nil })
|
36
|
+
@remote_ctrl_res = options.delete(:remote_ctrl_res) || (Proc.new {|r_ctrl_data| })
|
37
|
+
@error_res = options[:error_res] || (Proc.new {|err_data| })
|
38
|
+
@retry_count = options.delete(:retry_count) || 5
|
39
|
+
@heartbeat_interval = options.delete(:heartbeat_interval) || 60
|
40
|
+
@logger = options.delete(:logger) || Logger.new(STDOUT)
|
41
|
+
@heartbeat_thread = nil
|
42
|
+
@remote_ctrl_thread = nil
|
43
|
+
@socket = SnotiSocket.new(options)
|
44
|
+
@exited = false
|
45
|
+
end
|
46
|
+
|
47
|
+
def start
|
48
|
+
listen_shut_down
|
49
|
+
|
50
|
+
connect_count = 0
|
51
|
+
begin
|
52
|
+
connect_count += 1
|
53
|
+
# 1. Connect to Gizwits Snoti API
|
54
|
+
@socket.connect
|
55
|
+
# 2. Login to Gizwits Snoti API and Check
|
56
|
+
if @socket.login_ok?
|
57
|
+
# 3. Exchange data via SnotiSocket
|
58
|
+
exchange
|
59
|
+
else
|
60
|
+
raise LoginError
|
61
|
+
end
|
62
|
+
rescue Exception => e
|
63
|
+
if connect_count < @retry_count
|
64
|
+
retry
|
65
|
+
else
|
66
|
+
# It needs to kill the backgroud thread after all retry failed
|
67
|
+
dispose
|
68
|
+
raise e
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def exchange
|
75
|
+
# 1. Start heartbeat
|
76
|
+
heartbeat
|
77
|
+
# 2. Start remote control in another thread
|
78
|
+
invoke_remote_ctrl
|
79
|
+
# 3. Loop and fetch data from Gizwits Snoti API
|
80
|
+
smart_read
|
81
|
+
end
|
82
|
+
|
83
|
+
# Base on Gizwits Snoti API, it would be one message which detected by '\n'.
|
84
|
+
# So sometimes it needs to get the message via length = 1 when the app's data flow is not large.
|
85
|
+
def smart_read
|
86
|
+
nbytes = 100
|
87
|
+
receive_total_by_one_byte = 0
|
88
|
+
begin
|
89
|
+
loop do
|
90
|
+
if !@exited
|
91
|
+
handle_snoti_data(@socket.read(nbytes))
|
92
|
+
(receive_total_by_one_byte += 1) if (nbytes == 1)
|
93
|
+
(nbytes, receive_total_by_one_byte = 100, 0) if (receive_total_by_one_byte >= 100)
|
94
|
+
else
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
rescue ConnTimeoutError, ReadTimeoutError => timeout_ex
|
99
|
+
nbytes, receive_total_by_one_byte = 1, 0
|
100
|
+
retry
|
101
|
+
rescue Exception => ex
|
102
|
+
raise ex
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_snoti_data(noti_data)
|
107
|
+
json_data = JSON.parse(noti_data)
|
108
|
+
|
109
|
+
case json_data["cmd"]
|
110
|
+
when "event_push"
|
111
|
+
@socket.ack(json_data['delivery_id'])
|
112
|
+
@event_push.call(json_data)
|
113
|
+
when "remote_control_res"
|
114
|
+
@remote_ctrl_res.call(json_data)
|
115
|
+
when "invalid_msg"
|
116
|
+
@error_res.call(json_data)
|
117
|
+
when "pong"
|
118
|
+
# Nothing to do
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Keep heartbeat to Gizwits Snoti API every 60 seconds in backgroud thread
|
123
|
+
def heartbeat
|
124
|
+
if @heartbeat_thread.nil?
|
125
|
+
@heartbeat_thread = every(60) do
|
126
|
+
begin
|
127
|
+
@socket.ping if !@exited
|
128
|
+
rescue Exception => e
|
129
|
+
@logger.error("[Heartbeat Error] ====> #{e}")
|
130
|
+
end
|
131
|
+
|
132
|
+
# If it catch system exit signal, return true to break the loop
|
133
|
+
is_break = @exited
|
134
|
+
is_break
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Handle remote control request to Gizwits Snoti API in backgroud thread
|
140
|
+
def invoke_remote_ctrl
|
141
|
+
if @remote_ctrl_thread.nil?
|
142
|
+
@remote_ctrl_thread = loop_after(2) do
|
143
|
+
if !@exited
|
144
|
+
begin
|
145
|
+
r_ctrl_req = @remote_ctrl.call
|
146
|
+
if r_ctrl_req.nil?
|
147
|
+
sleep 1
|
148
|
+
else
|
149
|
+
@logger.info("[Remote Control] ====> #{r_ctrl_req}")
|
150
|
+
@socket.remote_control(r_ctrl_req)
|
151
|
+
end
|
152
|
+
rescue Exception => e
|
153
|
+
@logger.error("[Remote Control Error] =====> #{e}")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# If it catch system exit signal, return true to break the loop
|
158
|
+
is_break = @exited
|
159
|
+
is_break
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def every(interval)
|
165
|
+
Thread.new { loop { sleep interval; break if yield } }
|
166
|
+
end
|
167
|
+
|
168
|
+
def loop_after(seconds)
|
169
|
+
Thread.new { sleep seconds; loop { break if yield } }
|
170
|
+
end
|
171
|
+
|
172
|
+
def dispose
|
173
|
+
if @remote_ctrl_thread.nil?
|
174
|
+
@remote_ctrl_thread.kill
|
175
|
+
@remote_ctrl_thread = nil
|
176
|
+
end
|
177
|
+
|
178
|
+
if @heartbeat_thread.nil?
|
179
|
+
@heartbeat_thread.kill
|
180
|
+
@heartbeat_thread = nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def listen_shut_down
|
185
|
+
# Trap ^C
|
186
|
+
Signal.trap('INT') { @exited = true }
|
187
|
+
# Trap `Kill`
|
188
|
+
Signal.trap('TERM') { @exited = true }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "openssl"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
# GizwitsSac = Gizwits Snoti API Client
|
6
|
+
module GizwitsSac
|
7
|
+
# options = {
|
8
|
+
# host: "xxx",
|
9
|
+
# port: "xxx",
|
10
|
+
# connect_timeout: xx,
|
11
|
+
# read_timeout: xx,
|
12
|
+
# write_timeout: xx,
|
13
|
+
# prefetch_count: xx,
|
14
|
+
# auth_data: [
|
15
|
+
# {
|
16
|
+
# product_key: "xxxx",
|
17
|
+
# auth_id: "xxxx",
|
18
|
+
# auth_secret: "xxxx",
|
19
|
+
# subkey: "xxxx",
|
20
|
+
# events: ['xxx', 'xxx']
|
21
|
+
# }
|
22
|
+
# ]
|
23
|
+
# }
|
24
|
+
class SnotiSocket
|
25
|
+
def initialize(options)
|
26
|
+
@host = options[:host]
|
27
|
+
@port = options[:port]
|
28
|
+
@timeout = options[:connect_timeout] || 3
|
29
|
+
@read_timeout = options[:read_timeout] || @timeout
|
30
|
+
@write_timeout = options[:write_timeout] || @timeout
|
31
|
+
@prefetch_count = options[:prefetch_count] || 50
|
32
|
+
@auth_data = options[:auth_data]
|
33
|
+
@socket = nil
|
34
|
+
@ssl_socket = nil
|
35
|
+
@lf = "\n"
|
36
|
+
@buffer = "".dup
|
37
|
+
end
|
38
|
+
|
39
|
+
def connect
|
40
|
+
socket_connect
|
41
|
+
ssl_connect
|
42
|
+
end
|
43
|
+
|
44
|
+
def read(n_bytes = 1)
|
45
|
+
index = nil
|
46
|
+
while (index = @buffer.index(@lf)) == nil
|
47
|
+
@buffer << read_data(n_bytes)
|
48
|
+
end
|
49
|
+
@buffer.slice!(0, index + @lf.bytesize)
|
50
|
+
end
|
51
|
+
|
52
|
+
def write(data)
|
53
|
+
write_data(data)
|
54
|
+
end
|
55
|
+
|
56
|
+
def closed?
|
57
|
+
@socket.nil? && @ssl_socket.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def login_ok?
|
61
|
+
req_msg = "{\"cmd\":\"login_req\",\"prefetch_count\": #{@prefetch_count},\"data\": #{@auth_data.to_json}}"
|
62
|
+
write(req_msg)
|
63
|
+
resp =JSON.parse(read)
|
64
|
+
succeed = resp["data"]["result"]
|
65
|
+
# close connection if login failed
|
66
|
+
close if !succeed
|
67
|
+
return succeed
|
68
|
+
end
|
69
|
+
|
70
|
+
def remote_control(data_arr, msg_id = nil)
|
71
|
+
req = { cmd: "remote_control_req", data: data_arr }
|
72
|
+
req[:msg_id] = msg_id if !msg_id.nil?
|
73
|
+
write(req.to_json)
|
74
|
+
end
|
75
|
+
|
76
|
+
def ping
|
77
|
+
write("{\"cmd\": \"ping\"}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def ack(delivery_id)
|
81
|
+
write("{\"cmd\": \"event_ack\",\"delivery_id\": #{delivery_id}}")
|
82
|
+
end
|
83
|
+
|
84
|
+
def close
|
85
|
+
@ssl_socket.close if (!@ssl_socket.nil? && !@ssl_socket.closed?)
|
86
|
+
@ssl_socket = nil
|
87
|
+
@socket.close if (!@socket.nil? && !@socket.closed?)
|
88
|
+
@socket = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def socket_connect
|
94
|
+
addr = Socket.getaddrinfo(@host, nil)
|
95
|
+
socket_addr = Socket.pack_sockaddr_in(@port, addr[0][3])
|
96
|
+
|
97
|
+
@socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
|
98
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
99
|
+
|
100
|
+
begin
|
101
|
+
begin
|
102
|
+
non_blocking(socket, build_deadline(@timeout)) { socket.connect_nonblock(socket_addr) }
|
103
|
+
rescue Errno::EISCONN
|
104
|
+
# TODO: nothing to do
|
105
|
+
rescue ConnTimeoutError
|
106
|
+
raise ConnTimeoutError
|
107
|
+
end
|
108
|
+
rescue ConnTimeoutError => timeout_ex
|
109
|
+
raise ConnTimeoutError
|
110
|
+
rescue SystemCallError, IOError => exception
|
111
|
+
raise ConnFailedError.new("socket connection failed with exception: #{exception}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def non_blocking(socket, deadline)
|
117
|
+
yield
|
118
|
+
rescue IO::WaitReadable
|
119
|
+
timeout = check_deadline(deadline)
|
120
|
+
raise ConnTimeoutError unless IO.select([socket], nil, nil, timeout)
|
121
|
+
retry
|
122
|
+
rescue IO::WaitWritable
|
123
|
+
timeout = check_deadline(deadline)
|
124
|
+
raise ConnTimeoutError unless IO.select(nil, [socket], nil, timeout)
|
125
|
+
retry
|
126
|
+
end
|
127
|
+
|
128
|
+
def check_deadline(deadline)
|
129
|
+
remaining = deadline - Time.now.utc
|
130
|
+
raise ConnTimeoutError if remaining < 0
|
131
|
+
return remaining
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_deadline(timeout)
|
135
|
+
Time.now.utc + timeout
|
136
|
+
end
|
137
|
+
|
138
|
+
def ssl_connect
|
139
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
140
|
+
ssl_context.set_params({verify_mode: OpenSSL::SSL::VERIFY_NONE})
|
141
|
+
|
142
|
+
@ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, ssl_context)
|
143
|
+
@ssl_socket.sync_close = true
|
144
|
+
|
145
|
+
ssl_socket_connect("ssl connection failed.", @timeout) {@ssl_socket.connect_nonblock}
|
146
|
+
end
|
147
|
+
|
148
|
+
def ssl_socket_connect(failed_desc, timeout)
|
149
|
+
begin
|
150
|
+
begin
|
151
|
+
non_blocking(@ssl_socket, build_deadline(timeout)) { yield }
|
152
|
+
rescue Errno::EISCONN
|
153
|
+
# TODO: nothing to do
|
154
|
+
rescue ConnTimeoutError
|
155
|
+
raise ConnTimeoutError
|
156
|
+
end
|
157
|
+
rescue ConnTimeoutError => timeout_ex
|
158
|
+
raise ConnTimeoutError
|
159
|
+
rescue SystemCallError, OpenSSL::SSL::SSLError, IOError => exception
|
160
|
+
raise ConnFailedError.new("#{failed_desc}\n Exception trace: #{exception}")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def write_data(data)
|
165
|
+
ssl_socket_connect("write data failed.", @write_timeout) {@ssl_socket.write_nonblock("#{data}\n")}
|
166
|
+
end
|
167
|
+
|
168
|
+
def read_data(n_bytes)
|
169
|
+
ssl_socket_connect("read data failed.", @read_timeout) {@ssl_socket.read_nonblock(n_bytes)}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
data/lib/gizwits_sac.rb
ADDED
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gizwits_sac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Abel Lai
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-nav
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Gizwits Snoti API Client for Ruby.
|
84
|
+
email:
|
85
|
+
- yilaqingfeng@gamil.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- Gemfile.lock
|
93
|
+
- LICENSE
|
94
|
+
- README.md
|
95
|
+
- README.zh_cn.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- gizwits_sac.gemspec
|
100
|
+
- lib/gizwits_sac.rb
|
101
|
+
- lib/gizwits_sac/error.rb
|
102
|
+
- lib/gizwits_sac/snoti_client.rb
|
103
|
+
- lib/gizwits_sac/snoti_socket.rb
|
104
|
+
- lib/gizwits_sac/version.rb
|
105
|
+
homepage: https://github.com/AbelLai/gizwits_sac_rb
|
106
|
+
licenses: []
|
107
|
+
metadata:
|
108
|
+
allowed_push_host: https://rubygems.org
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.5.1
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: Gizwits Snoti API Client
|
129
|
+
test_files: []
|