ipcam 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,380 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # Sample for v4l2-ruby
6
+ #
7
+ # Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ require 'fileutils'
11
+ require 'em-websocket'
12
+ require 'msgpack/rpc/server'
13
+
14
+ module IPCam
15
+ class WebSocket
16
+ include MessagePack::Rpc::Server
17
+
18
+ msgpack_options :symbolize_keys => true
19
+
20
+ class << self
21
+ #
22
+ # セッションリストの取得
23
+ #
24
+ # @return [Array<WebSocket>] セッションリスト
25
+ #
26
+ def session_list
27
+ return @session_list ||= []
28
+ end
29
+ private :session_list
30
+
31
+ #
32
+ # クリティカルセクションの設置
33
+ #
34
+ # @yield クリティカルセクションとして処理するブロック
35
+ #
36
+ # @return [Object] ブロックの戻り値
37
+ #
38
+ def sync(&proc)
39
+ return (@mutex ||= Mutex.new).synchronize(&proc)
40
+ end
41
+
42
+ #
43
+ # セッションリストへのセッション追加
44
+ #
45
+ # @param [Socket] sock セッションリストに追加するソケットオブジェクト
46
+ #
47
+ # @return [WebSocket]
48
+ # ソケットオブジェクトに紐付けられたセッションオブジェクト
49
+ #
50
+ # @note
51
+ # 受け取ったソケットオブジェクトを元に、セッションオブジェクトを
52
+ # 生成し、そのオブジェクトをセッションリストに追加する
53
+ #
54
+ def join(sock)
55
+ sync {
56
+ if session_list.any? {|s| s === sock}
57
+ raise("Already joined #{sock}")
58
+ end
59
+
60
+ ret = self.new(@app, sock)
61
+ session_list << ret
62
+
63
+ return ret
64
+ }
65
+ end
66
+ private :join
67
+
68
+ #
69
+ # セッションオブジェクトからのセッション削除
70
+ #
71
+ # @param [Socket] sock
72
+ # セッションオブジェクトを特定するためのソケットオブジェクト
73
+ #
74
+ def bye(sock)
75
+ sync {
76
+ session_list.reject! { |s|
77
+ if s === sock
78
+ s.finish
79
+ true
80
+ else
81
+ false
82
+ end
83
+ }
84
+ }
85
+ end
86
+
87
+ #
88
+ # イベント情報の一斉送信
89
+ #
90
+ # @param [String] name イベント名
91
+ # @param [Array] args イベントで通知する引数
92
+ #
93
+ def broadcast(name, *args)
94
+ sync {session_list.each {|s| s.notify(name, *args)}}
95
+ end
96
+
97
+ #
98
+ # バインド先のURL文字列を生成する
99
+ #
100
+ # @return [String] URL文字列
101
+ #
102
+ def bind_url
103
+ if $bind_addr.include?(":")
104
+ addr = "[#{$bind_addr}]" if $bind_addr.include?(":")
105
+ else
106
+ addr = $bind_addr
107
+ end
108
+
109
+ return "tcp://#{addr}:#{$http_port}"
110
+ end
111
+ private :bind_url
112
+
113
+ #
114
+ # WebSocket制御の開始
115
+ #
116
+ def start(app)
117
+ EM.defer {
118
+ @app = app
119
+
120
+ sleep 1 until EM.reactor_running?
121
+
122
+ $logger.info("websock") {"started (#{bind_url()})"}
123
+
124
+ EM::WebSocket.start(:host => $bind_addr, :port => $ws_port) { |sock|
125
+ peer = Socket.unpack_sockaddr_in(sock.get_peername)
126
+ addr = peer[1]
127
+ port = peer[0]
128
+ serv = join(sock)
129
+
130
+ sock.set_sock_opt(Socket::Constants::SOL_SOCKET,
131
+ Socket::SO_KEEPALIVE,
132
+ true)
133
+
134
+ sock.set_sock_opt(Socket::IPPROTO_TCP,
135
+ Socket::TCP_QUICKACK,
136
+ true)
137
+
138
+ sock.set_sock_opt(Socket::IPPROTO_TCP,
139
+ Socket::TCP_NODELAY,
140
+ false)
141
+
142
+ sock.onopen {
143
+ $logger.info("websock") {"connection from #{addr}:#{port}"}
144
+ }
145
+
146
+ sock.onbinary { |msg|
147
+ begin
148
+ serv.receive_dgram(msg)
149
+
150
+ rescue => e
151
+ $logger.error("websock") {
152
+ "error occured: #{e.message} (#{e.backtrace[0]})"
153
+ }
154
+ end
155
+ }
156
+
157
+ sock.onclose {
158
+ $logger.info("websock") {
159
+ "connection close from #{addr}:#{port}"
160
+ }
161
+
162
+ bye(sock)
163
+ }
164
+ }
165
+ }
166
+ end
167
+ end
168
+
169
+ #
170
+ # セッションオブジェクトのイニシャライザ
171
+ #
172
+ # @param [IPCam] app アプリケーション本体のインスタンス
173
+ # @param [Socket] sock Socketインスタンス
174
+ #
175
+ def initialize(app, sock)
176
+ @app = app
177
+ @sock = sock
178
+ @allow = []
179
+
180
+ peer = Socket.unpack_sockaddr_in(sock.get_peername)
181
+ @addr = peer[1]
182
+ @port = peer[0]
183
+ end
184
+
185
+ attr_reader :sock
186
+
187
+ #
188
+ # セッションオブジェクトの終了処理
189
+ #
190
+ def finish
191
+ end
192
+
193
+ #
194
+ # peerソケットへのデータ送信
195
+ #
196
+ # @param [String] data 送信するデータ
197
+ #
198
+ # @note MessagePack::Rpc::Serverのオーバーライド
199
+ #
200
+ def send_data(data)
201
+ @sock.send_binary(data)
202
+ end
203
+ private :send_data
204
+
205
+ #
206
+ # MessagePack-RPCのエラーハンドリング
207
+ #
208
+ # @param [StandardError] e 発生したエラーの例外オブジェクト
209
+ #
210
+ # @note MessagePack::Rpc::Serverのオーバーライド
211
+ #
212
+ def on_error(e)
213
+ $logger.error("websock") {e.message}
214
+ end
215
+ private :on_error
216
+
217
+ #
218
+ # 通知のブロードキャスト
219
+ #
220
+ # @param [String] name イベント名
221
+ # @param [Array] args イベントで通知する引数
222
+ #
223
+ def broadcast(name, *args)
224
+ self.class.broadcast(name, *arg)
225
+ end
226
+ private :broadcast
227
+
228
+ #
229
+ # 通知の送信
230
+ #
231
+ # @param [String] name イベント名
232
+ # @param [Array] args イベントで通知する引数
233
+ #
234
+ def notify(name, *args)
235
+ super(name, *args) if @allow == "*" or @allow.include?(name)
236
+ end
237
+
238
+ #
239
+ # 比較演算子の定義
240
+ #
241
+ def ===(obj)
242
+ return (self == obj || @sock == obj)
243
+ end
244
+
245
+ #
246
+ # RPC procedures
247
+ #
248
+
249
+ #
250
+ # 通知要求を設定する
251
+ #
252
+ # @param [Array] arg
253
+ #
254
+ # @return [:OK] 固定値
255
+ #
256
+ def add_notify_request(*args)
257
+ args.each {|type| @allow << type.to_sym}
258
+ args.uniq!
259
+
260
+ return :OK
261
+ end
262
+ remote_public :add_notify_request
263
+
264
+ #
265
+ # 通知要求をクリアする
266
+ #
267
+ # @param [Array] arg
268
+ #
269
+ # @return [:OK] 固定値
270
+ #
271
+ def clear_notify_request(*args)
272
+ args.each {|type| @allow.delete(type.to_sym)}
273
+
274
+ return :OK
275
+ end
276
+ remote_public :clear_notify_request
277
+
278
+ #
279
+ # 疎通確認用プロシジャー
280
+ #
281
+ # @return [:OK] 固定値
282
+ #
283
+ def hello
284
+ return :OK
285
+ end
286
+ remote_public :hello
287
+
288
+ #
289
+ # カメラ情報の取得
290
+ #
291
+ # @return [:OK] カメラ情報をパックしたハッシュ
292
+ #
293
+ def get_camera_info
294
+ return @app.get_camera_info()
295
+ end
296
+ remote_public :get_camera_info
297
+
298
+ #
299
+ # カメラ固有名の取得
300
+ #
301
+ # @return [:OK] カメラ情報をパックしたハッシュ
302
+ #
303
+ def get_ident_string
304
+ return @app.get_ident_string()
305
+ end
306
+ remote_public :get_ident_string
307
+
308
+ #
309
+ # カメラの設定情報の取得
310
+ #
311
+ # @return [Array] カメラの設定情報の配列
312
+ #
313
+ def get_config
314
+ return @app.get_config
315
+ end
316
+ remote_public :get_config
317
+
318
+ #
319
+ # 画像サイズの設定
320
+ #
321
+ # @param [Integer] width 新しい画像の幅
322
+ # @param [Integer] height 新しい画像の高さ
323
+ #
324
+ # @return [:OK] 固定値
325
+ #
326
+ # @note 画像サイズの変更に伴い、update_image_sizeイベントがブロード
327
+ # キャストされる
328
+ #
329
+ def set_image_size(width, height)
330
+ @app.set_image_size(width, height)
331
+ return :OK
332
+ end
333
+ remote_public :set_image_size
334
+
335
+ #
336
+ # フレームレートの設定
337
+ #
338
+ # @param [Integer] num 新しいフレームレートの値(分子)
339
+ # @param [Integer] deno 新しいフレームレートの値(分母)
340
+ #
341
+ # @return [:OK] 固定値
342
+ #
343
+ # @note 画像サイズの変更に伴い、update_framerateイベントがブロード
344
+ # キャストされる
345
+ #
346
+ def set_framerate(num, deno)
347
+ @app.set_framerate(num, deno)
348
+ return :OK
349
+ end
350
+ remote_public :set_framerate
351
+
352
+ #
353
+ # カメラの設定変更
354
+ #
355
+ # @param [Integer] id 設定項目のID
356
+ # @param [Integer] val 新しい設定項目の値
357
+ #
358
+ # @return [:OK] 固定値
359
+ #
360
+ # @note 画像サイズの変更に伴い、update_controlイベントがブロード
361
+ # キャストされる
362
+ #
363
+ def set_control(num, deno)
364
+ @app.set_control(num, deno)
365
+ return :OK
366
+ end
367
+ remote_public :set_control
368
+
369
+ #
370
+ # 設定値の保存
371
+ #
372
+ # @return [:OK] 固定値
373
+ #
374
+ def save_config
375
+ @app.save_config()
376
+ return :OK
377
+ end
378
+ remote_public :save_config
379
+ end
380
+ end
@@ -0,0 +1,191 @@
1
+ /*
2
+ * MessagePack RPC base class
3
+ *
4
+ * (C) 2017 Hiroshi Kuwagata <kgt9221@gmail.com>
5
+ */
6
+
7
+ if (!msgpack) {
8
+ throw "msgpack-lite is not load yet"
9
+ }
10
+
11
+ (function () {
12
+ /* declar local symbol */
13
+ const newId = Symbol('newId');
14
+ const callback = Symbol('callback');
15
+ const recv = Symbol('recv');
16
+
17
+ /* declar contant */
18
+ const DEFAULT_URL = `ws://${location.hostname}:${parseInt(location.port)+1}/`;
19
+ const COEDEC = msgpack.createCodec({binarraybuffer:true, preset:true});
20
+ const ENC_OPT = {codec:COEDEC};
21
+
22
+ msgpack.rpc = class {
23
+ /*
24
+ * core functions
25
+ */
26
+ constructor (url) {
27
+ this.url = url || DEFAULT_URL
28
+ this.sock = null;
29
+ this.deferred = null;
30
+ this.maxId = 1;
31
+ this.handlers = {}
32
+ }
33
+
34
+ [newId]() {
35
+ return this.maxId++;
36
+ }
37
+
38
+ [callback](name, args) {
39
+ if (this.handlers[name]) {
40
+ this.handlers[name](...args);
41
+ } else {
42
+ throw `unhandled notification received (${name}).`;
43
+ }
44
+ }
45
+
46
+ [recv](data) {
47
+ var self;
48
+ var msg;
49
+ var type;
50
+ var id;
51
+ var meth;
52
+ var err;
53
+ var res;
54
+ var para;
55
+ var $df;
56
+
57
+ msg = msgpack.decode(new Uint8Array(data), ENC_OPT);
58
+ type = msg[0];
59
+
60
+ switch (type) {
61
+ case 1:
62
+ id = msg[1];
63
+ err = msg[2];
64
+ res = msg[3];
65
+ $df = this.deferred[id];
66
+
67
+ if ($df) {
68
+ if (err) {
69
+ $df.reject(err);
70
+ } else {
71
+ $df.resolve(res);
72
+ }
73
+ }
74
+
75
+ delete this.deferred[id];
76
+ break;
77
+
78
+ case 2:
79
+ meth = msg[1];
80
+ para = msg[2];
81
+
82
+ if (!para) {
83
+ para = [];
84
+ } else if (!(para instanceof Array)) {
85
+ para = [para];
86
+ }
87
+
88
+ this[callback](meth, para);
89
+ break;
90
+
91
+ default:
92
+ throw `Illeagal data (type=${type}) recevied.`;
93
+ }
94
+ }
95
+
96
+ remoteCall(meth) {
97
+ var id;
98
+ var $df;
99
+ var args;
100
+
101
+ id = this[newId]();
102
+ $df = new $.Deferred();
103
+ args = Array.prototype.slice.call(arguments, 1);
104
+
105
+ switch (args.length) {
106
+ case 0:
107
+ args = null;
108
+ break;
109
+
110
+ case 1:
111
+ args = args[0];
112
+ break;
113
+ }
114
+
115
+ this.deferred[id] = $df;
116
+ this.sock.send(msgpack.encode([0, id, meth, args], ENC_OPT));
117
+
118
+ return $df.promise();
119
+ }
120
+
121
+ remoteNotify(meth) {
122
+ var args;
123
+
124
+ args = Array.prototype.slice.call(arguments, 1);
125
+
126
+ switch (args.length) {
127
+ case 0:
128
+ args = null;
129
+ break;
130
+
131
+ case 1:
132
+ args = args[0];
133
+ break;
134
+ }
135
+
136
+ this.sock.send(msgpack.encode([2, meth, args], ENC_OPT));
137
+ }
138
+
139
+ on(name, func) {
140
+ this.handlers[name] = func;
141
+ return this;
142
+ }
143
+
144
+ start() {
145
+ var $df;
146
+
147
+ $df = new $.Deferred();
148
+
149
+ if (!this.sock) {
150
+ this.sock = new WebSocket(this.url);
151
+
152
+ this.sock.binaryType = "arraybuffer";
153
+
154
+ this.sock.onopen = () => {
155
+ this.deferred = {}
156
+ $df.resolve();
157
+ };
158
+
159
+ this.sock.onerror = () => {
160
+ $df.reject();
161
+ };
162
+
163
+ this.sock.onmessage = (m) => {
164
+ this[recv](m.data);
165
+ };
166
+
167
+ this.sock.onclose = () => {
168
+ if (this.onSessionClosed) this.onSessionClosed();
169
+ this.sock = null;
170
+ };
171
+ }
172
+
173
+ return $df.promise();
174
+ }
175
+
176
+ finish() {
177
+ var id;
178
+
179
+ this.sock.close();
180
+
181
+ for (id in this.deferred) {
182
+ this.deferred[id].reject("session finished");
183
+ delete this.deferred[id];
184
+ }
185
+
186
+ this.sock = null;
187
+ this.deferred = null;
188
+ }
189
+ }
190
+ })();
191
+
@@ -0,0 +1,135 @@
1
+ /*
2
+ * 雑多な処理を集めたコード
3
+ *
4
+ * (C) 2017 Hiroshi Kuwagata <kgt9221@gmail.com>
5
+ */
6
+
7
+ (function () {
8
+ function readJavaScript(src) {
9
+ return $.getScript(src);
10
+ }
11
+
12
+ function readCss(src) {
13
+ var $df;
14
+
15
+ $df = new $.Deferred();
16
+
17
+ $('head')
18
+ .append($('<link>')
19
+ .on('load', () => {
20
+ $df.resolve();
21
+ })
22
+ .attr("rel", "stylesheet")
23
+ .attr("type", "text/css")
24
+ .attr("href", src)
25
+ );
26
+
27
+ return $df.promise();
28
+ }
29
+
30
+ function getResource(list, $df) {
31
+ var src;
32
+
33
+ src = list.shift();
34
+
35
+ if (/\.js$/.test(src)) {
36
+ readJavaScript(src)
37
+ .then((script, state) => {
38
+ getResource(list, $df);
39
+ })
40
+ .fail((error) => {
41
+ $df.reject(error);
42
+ });
43
+
44
+ } else if (/\.(css|scss)$/.test(src)) {
45
+ readCss(src)
46
+ .then(() => {
47
+ getResource(list, $df);
48
+ })
49
+ .fail((error) => {
50
+ $df.reject(error);
51
+ });
52
+
53
+ } else if (src == null) {
54
+ $df.resolve();
55
+ }
56
+ }
57
+
58
+ function loadImage(url, dst) {
59
+ var $df;
60
+
61
+ $df = new $.Deferred();
62
+
63
+ if (!dst) {
64
+ dst = new Image();
65
+ } else {
66
+ if (dst instanceof jQuery) {
67
+ dst = dst[0];
68
+ }
69
+
70
+ if (!(dst instanceof Image)) {
71
+ throw("not image object");
72
+ }
73
+ }
74
+
75
+ $(dst)
76
+ .on('load', () => {
77
+ $df.resolve(dst);
78
+ })
79
+ .on('error', (e) => {
80
+ $df.reject(e);
81
+ })
82
+ .attr('src', url);
83
+
84
+ return $df.promise();
85
+ }
86
+
87
+ Utils = class {
88
+ static require(list) {
89
+ var $df;
90
+
91
+ $df = new $.Deferred()
92
+
93
+ getResource(list, $df);
94
+
95
+ return $df.promise();
96
+ }
97
+
98
+ static loadImageFromData(data) {
99
+ var $df;
100
+ var blob;
101
+ var url;
102
+
103
+ $df = new $.Deferred();
104
+ blob = new Blob([data.data], {type: data.type});
105
+ url = URL.createObjectURL(blob);
106
+
107
+ loadImage(url)
108
+ .then((img) => {
109
+ $df.resolve(img);
110
+ })
111
+ .fail((error) => {
112
+ $df.reject(error);
113
+ })
114
+ .always(() => {
115
+ URL.revokeObjectURL(url);
116
+ });
117
+
118
+ return $df.promise();
119
+ }
120
+
121
+ static copyToClipboard(text) {
122
+ var $text;
123
+
124
+ $text = $('<textarea>').css('visible', 'hidden');
125
+ $('body').append($text);
126
+
127
+ $text
128
+ .val(text)
129
+ .select();
130
+ document.execCommand('copy');
131
+
132
+ $text.remove();
133
+ }
134
+ }
135
+ })();