fluent-plugin-everysense 0.0.7 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/LICENSE +191 -21
- data/README.md +58 -41
- data/Rakefile +1 -1
- data/examples/everystamp/README.md +3 -1
- data/fluent-plugin-everysense.gemspec +8 -6
- data/lib/fluent/plugin/everysense_proxy.rb +19 -28
- data/lib/fluent/plugin/filter_everysense.rb +13 -10
- data/lib/fluent/plugin/in_everysense.rb +106 -75
- data/lib/fluent/plugin/out_everysense.rb +128 -89
- data/tutorial/ja/json_over_http_api_tutorial.md +10 -2
- metadata +26 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3dd0be119277f2fb805c2fad1aa564fc92b45a8b
|
4
|
+
data.tar.gz: 2153d7ad6bbf73d36c5426f1cd16cc4b0f3cb3ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79f83a1a7912eee72d7544b331b2909118b1de946f4de2b279a6c4db2403c3aa5bee759fa5bd4a739b3cb1a9e0ff2e3d4f22a1819f91a32697288c87ef13ef4f
|
7
|
+
data.tar.gz: c56f8ff65e2fa979c675d93d71f7ea3b20798cb5cb26daa05fa633d4c495075162babe02ef6097f5556a8e7c8e274fe19bd03231ff2f450d7d5d75fb955ec200
|
data/CHANGELOG.md
ADDED
data/LICENSE
CHANGED
@@ -1,21 +1,191 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
1
|
+
|
2
|
+
Apache License
|
3
|
+
Version 2.0, January 2004
|
4
|
+
https://www.apache.org/licenses/
|
5
|
+
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
7
|
+
|
8
|
+
1. Definitions.
|
9
|
+
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
12
|
+
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
14
|
+
the copyright owner that is granting the License.
|
15
|
+
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
17
|
+
other entities that control, are controlled by, or are under common
|
18
|
+
control with that entity. For the purposes of this definition,
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
20
|
+
direction or management of such entity, whether by contract or
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
23
|
+
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
25
|
+
exercising permissions granted by this License.
|
26
|
+
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
28
|
+
including but not limited to software source code, documentation
|
29
|
+
source, and configuration files.
|
30
|
+
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
32
|
+
transformation or translation of a Source form, including but
|
33
|
+
not limited to compiled object code, generated documentation,
|
34
|
+
and conversions to other media types.
|
35
|
+
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
37
|
+
Object form, made available under the License, as indicated by a
|
38
|
+
copyright notice that is included in or attached to the work
|
39
|
+
(an example is provided in the Appendix below).
|
40
|
+
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
47
|
+
the Work and Derivative Works thereof.
|
48
|
+
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
50
|
+
the original version of the Work and any modifications or additions
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
62
|
+
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
65
|
+
subsequently incorporated within the Work.
|
66
|
+
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
73
|
+
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
79
|
+
where such license applies only to those patent claims licensable
|
80
|
+
by such Contributor that are necessarily infringed by their
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
83
|
+
institute patent litigation against any entity (including a
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
86
|
+
or contributory patent infringement, then any patent licenses
|
87
|
+
granted to You under this License for that Work shall terminate
|
88
|
+
as of the date such litigation is filed.
|
89
|
+
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
92
|
+
modifications, and in Source or Object form, provided that You
|
93
|
+
meet the following conditions:
|
94
|
+
|
95
|
+
(a) You must give any other recipients of the Work or
|
96
|
+
Derivative Works a copy of this License; and
|
97
|
+
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
99
|
+
stating that You changed the files; and
|
100
|
+
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
103
|
+
attribution notices from the Source form of the Work,
|
104
|
+
excluding those notices that do not pertain to any part of
|
105
|
+
the Derivative Works; and
|
106
|
+
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
109
|
+
include a readable copy of the attribution notices contained
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
112
|
+
of the following places: within a NOTICE text file distributed
|
113
|
+
as part of the Derivative Works; within the Source form or
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
115
|
+
within a display generated by the Derivative Works, if and
|
116
|
+
wherever such third-party notices normally appear. The contents
|
117
|
+
of the NOTICE file are for informational purposes only and
|
118
|
+
do not modify the License. You may add Your own attribution
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
121
|
+
that such additional attribution notices cannot be construed
|
122
|
+
as modifying the License.
|
123
|
+
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
125
|
+
may provide additional or different license terms and conditions
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
129
|
+
the conditions stated in this License.
|
130
|
+
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
134
|
+
this License, without any additional terms or conditions.
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
136
|
+
the terms of any separate license agreement you may have executed
|
137
|
+
with Licensor regarding such Contributions.
|
138
|
+
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
141
|
+
except as required for reasonable and customary use in describing the
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
143
|
+
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
153
|
+
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
159
|
+
incidental, or consequential damages of any character arising as a
|
160
|
+
result of this License or out of the use or inability to use the
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
163
|
+
other commercial damages or losses), even if such Contributor
|
164
|
+
has been advised of the possibility of such damages.
|
165
|
+
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
169
|
+
or other liability obligations and/or rights consistent with this
|
170
|
+
License. However, in accepting such obligations, You may act only
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
175
|
+
of your accepting any such warranty or additional liability.
|
176
|
+
|
177
|
+
END OF TERMS AND CONDITIONS
|
178
|
+
|
179
|
+
Copyright 2016-2017 Toyokazu Akiyama
|
180
|
+
|
181
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
182
|
+
you may not use this file except in compliance with the License.
|
183
|
+
You may obtain a copy of the License at
|
184
|
+
|
185
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
186
|
+
|
187
|
+
Unless required by applicable law or agreed to in writing, software
|
188
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
189
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
190
|
+
See the License for the specific language governing permissions and
|
191
|
+
limitations under the License.
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ gem install fluent-plugin-everysense
|
|
20
20
|
fluent-plugin-everysense has Input and Output Plugins for EverySense platform.
|
21
21
|
|
22
22
|
|
23
|
-
### Input Plugin (Fluent::EverySenseInput)
|
23
|
+
### Input Plugin (Fluent::Plugin::EverySenseInput)
|
24
24
|
|
25
25
|
Input Plugin can receive events from EverySense Server. It can be used via source directive in the configuration.
|
26
26
|
|
@@ -44,32 +44,40 @@ Input Plugin can receive events from EverySense Server. It can be used via sourc
|
|
44
44
|
- **recipe_id** (device_id or recipe_id is required): the target recipe id to obtain input data
|
45
45
|
- **polling_interval**: interval to poll EverySense JSON over HTTP API
|
46
46
|
|
47
|
-
|
47
|
+
Time field is added for every sensor in EverySense because sometimes remotely deployed sensors are integrated into one device. So, time field of the whole device is generated when Input Plugin received data from EverySense. It can be used only for inside the fluentd network. The real timestamp for each sensors is recorded inside JSON data and not synchronized to the time field. An example record format of the Input Plugin is as follows:
|
48
48
|
|
49
49
|
```
|
50
|
-
|
50
|
+
# device with farm_uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
|
51
|
+
{
|
52
|
+
"farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
53
|
+
"device":
|
51
54
|
[
|
52
|
-
|
55
|
+
# list of sensors connected to this device
|
56
|
+
{ "farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
57
|
+
"data": {
|
53
58
|
"at": "2016-05-15 12:14:30 +0900",
|
54
59
|
"unit":"degree Celsius",
|
55
60
|
"value":23
|
56
61
|
},
|
57
|
-
"sensor_name":"collection_data_1"
|
62
|
+
"sensor_name":"collection_data_1",
|
63
|
+
"data_class_name":"AirTemperature"
|
58
64
|
},
|
59
|
-
{ "
|
65
|
+
{ "farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
66
|
+
"data": {
|
60
67
|
"at":"2016-05-15 12:14:30 +0900",
|
61
68
|
"unit":"%RH",
|
62
69
|
"value":30
|
63
70
|
},
|
64
|
-
"sensor_name":"collection_data_2"
|
71
|
+
"sensor_name":"collection_data_2",
|
72
|
+
"data_class_name":"AirHygrometer"
|
65
73
|
}
|
66
74
|
]
|
67
75
|
}
|
68
76
|
```
|
69
77
|
|
70
|
-
While fluentd record must be a map (Hash), the output of
|
78
|
+
While fluentd record must be a map (Hash), the output of a farm, which means a set of sensors in other words a virtual device, becomes an array of sensor data. So, the keys "farm_uuid" and "device" are added to make it as a map.
|
71
79
|
|
72
|
-
### Output Plugin (Fluent::EverySenseOutput)
|
80
|
+
### Output Plugin (Fluent::Plugin::EverySenseOutput)
|
73
81
|
|
74
82
|
Output Plugin can send events to EverySense server. It can be used via match directive. Output Plugin assumes the input format as described above.
|
75
83
|
|
@@ -98,15 +106,14 @@ Output Plugin can send events to EverySense server. It can be used via match dir
|
|
98
106
|
- **password** (required): password for the login_name
|
99
107
|
- **device_id** (required): The target device id to submit sensor data
|
100
108
|
- **flush_interval**: Upload interval (default: 30)
|
101
|
-
- **aggr_type**: Buffered sensor data can be aggregated with specified statistic method. Currently avg (average) and none (without aggregation) can be specified (default: none).
|
102
109
|
- **sensor** (required): Output sensor names must be specified by sensor directive. Sensor data with the specified input_name will be uploaded to EverySense server with the output_name. If the input device data includes multiple sensor data, multiple directives can be specified. If sensor_name of input sensor data is not specified in the configuration, that sensor data will be skipped. If the sensor_names of input data are nil, sensor data will be uploaded with the number of sensor directives with the specified output_name.
|
103
110
|
- **input_name**: sensor_name of the input sensor data.
|
104
111
|
- **output_name**: sensor_name of the output sensor data.
|
105
112
|
- **type_of_value**: type of value (default: Integer)
|
106
113
|
|
107
|
-
## Filter Plugin (Fluent::EverySenseFilter)
|
114
|
+
## Filter Plugin (Fluent::Plugin::EverySenseFilter)
|
108
115
|
|
109
|
-
Filter Plugin can split an EverySense Server
|
116
|
+
Filter Plugin can split an EverySense Server device data into multiple fluentd events if it has multiple sensor data. It can be used via filter directive.
|
110
117
|
|
111
118
|
```
|
112
119
|
<filter tag_name>
|
@@ -114,54 +121,64 @@ Filter Plugin can split an EverySense Server event into multiple fluentd events
|
|
114
121
|
</filter>
|
115
122
|
```
|
116
123
|
|
117
|
-
The following input data from EverySense Server will be
|
124
|
+
The following input data from EverySense Server will be split into multiple fluentd events.
|
125
|
+
|
126
|
+
Target device data with multiple sensors in fluentd event form:
|
118
127
|
|
119
128
|
```
|
120
|
-
{
|
129
|
+
{
|
130
|
+
"farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
131
|
+
"device":
|
121
132
|
[
|
122
133
|
{ "farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
123
|
-
"sensor_name":"collection_data_1",
|
124
|
-
"data_class_name":"Illuminance",
|
125
134
|
"data": {
|
126
|
-
"at":"2016-
|
127
|
-
"
|
128
|
-
"
|
129
|
-
}
|
135
|
+
"at": "2016-05-15 12:14:30 +0900",
|
136
|
+
"unit":"degree Celsius",
|
137
|
+
"value":23
|
138
|
+
},
|
139
|
+
"sensor_name":"collection_data_1",
|
140
|
+
"data_class_name":"AirTemperature"
|
130
141
|
},
|
131
142
|
{ "farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
132
|
-
"sensor_name":"collection_data_2",
|
133
|
-
"data_class_name":"Location",
|
134
143
|
"data": {
|
135
|
-
"at":"2016-
|
136
|
-
"
|
137
|
-
"
|
138
|
-
}
|
144
|
+
"at":"2016-05-15 12:14:30 +0900",
|
145
|
+
"unit":"%RH",
|
146
|
+
"value":30
|
147
|
+
},
|
148
|
+
"sensor_name":"collection_data_2",
|
149
|
+
"data_class_name":"AirHygrometer"
|
139
150
|
}
|
140
151
|
]
|
141
152
|
}
|
142
153
|
```
|
143
154
|
|
155
|
+
will be split into the following fluentd events.
|
156
|
+
|
144
157
|
```
|
145
|
-
{
|
158
|
+
{
|
159
|
+
"farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
160
|
+
"data": {
|
161
|
+
"at": "2016-05-15 12:14:30 +0900",
|
162
|
+
"unit":"degree Celsius",
|
163
|
+
"value":23
|
164
|
+
},
|
146
165
|
"sensor_name":"collection_data_1",
|
147
|
-
"data_class_name":"
|
166
|
+
"data_class_name":"AirTemperature"
|
167
|
+
}
|
168
|
+
{
|
169
|
+
"farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
148
170
|
"data": {
|
149
|
-
"at":"2016-
|
150
|
-
"
|
151
|
-
"
|
152
|
-
}
|
153
|
-
},
|
154
|
-
{ "farm_uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
|
171
|
+
"at":"2016-05-15 12:14:30 +0900",
|
172
|
+
"unit":"%RH",
|
173
|
+
"value":30
|
174
|
+
},
|
155
175
|
"sensor_name":"collection_data_2",
|
156
|
-
"data_class_name":"
|
157
|
-
"data": {
|
158
|
-
"at":"2016-08-24 00:15:00 UTC",
|
159
|
-
"values":[138.442062,35.8162422,671.599975],
|
160
|
-
"unit":"degree"
|
161
|
-
}
|
176
|
+
"data_class_name":"AirHygrometer"
|
162
177
|
}
|
163
178
|
```
|
164
179
|
|
180
|
+
This filter can be used to store data into a time series database, e.g. Elasticsearch.
|
181
|
+
|
165
182
|
## Contributing
|
166
183
|
|
167
184
|
1. Fork it ( http://github.com/toyokazu/fluent-plugin-everysense/fork )
|
@@ -173,4 +190,4 @@ The following input data from EverySense Server will be splitted into multiple f
|
|
173
190
|
|
174
191
|
## License
|
175
192
|
|
176
|
-
The gem is available as open source under the terms of the [
|
193
|
+
The gem is available as open source under the terms of the [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
data/Rakefile
CHANGED
@@ -11,7 +11,7 @@ https://www.elastic.co/downloads/elasticsearch
|
|
11
11
|
|
12
12
|
Linuxの場合は以下のページにある方法でパッケージ管理システム (apt, yum) を用いてインストールするのが簡単です.
|
13
13
|
|
14
|
-
https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-
|
14
|
+
https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-repositories.html
|
15
15
|
|
16
16
|
Kibanaも同様にパッケージ管理システムを用いてインストールできます.
|
17
17
|
|
@@ -19,6 +19,8 @@ https://www.elastic.co/guide/en/kibana/4.5/_upgrading_kibana.html
|
|
19
19
|
|
20
20
|
インストールが完了したらサービスを開始します.
|
21
21
|
|
22
|
+
https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-service.html
|
23
|
+
|
22
24
|
```
|
23
25
|
sudo /bin/systemctl daemon-reload
|
24
26
|
sudo /bin/systemctl enable elasticsearch.service
|
@@ -4,25 +4,27 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "fluent-plugin-everysense"
|
7
|
-
spec.version = "0.0
|
7
|
+
spec.version = "0.1.0"
|
8
8
|
spec.authors = ["Toyokazu Akiyama"]
|
9
9
|
spec.email = ["toyokazu@gmail.com"]
|
10
10
|
|
11
11
|
spec.summary = %q{Fluent Input/Output plugin for EverySense Framework}
|
12
12
|
spec.description = %q{Fluent Input/Output plugin for EverySense Framework}
|
13
13
|
spec.homepage = "https://github.com/toyokazu/fluent-plugin-everysense"
|
14
|
-
spec.license = "
|
14
|
+
spec.license = "Apache License Version 2.0"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.gsub(/.+images\/[\w\.-]+\n/, "").split($/)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.required_ruby_version = '>= 2.
|
21
|
+
spec.required_ruby_version = '>= 2.2.0'
|
22
22
|
|
23
|
-
spec.add_dependency 'fluentd', '~> 0.
|
23
|
+
spec.add_dependency 'fluentd', '~> 0.14.0'
|
24
|
+
#spec.add_dependency 'fluentd', '~> 0.12.0'
|
24
25
|
#spec.add_dependency 'fluentd', '>= 0.10.0'
|
25
26
|
|
26
|
-
spec.add_development_dependency "bundler", "~> 1.
|
27
|
-
spec.add_development_dependency "rake", "~>
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
28
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
29
|
+
spec.add_development_dependency "test-unit"
|
28
30
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Fluent
|
1
|
+
module Fluent::Plugin
|
2
2
|
module EverySenseProxy
|
3
3
|
require 'uri'
|
4
4
|
require 'net/http'
|
@@ -8,22 +8,8 @@ module Fluent
|
|
8
8
|
class EverySenseProxyError
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.included(base)
|
12
|
-
base.desc 'EverySense API URI'
|
13
|
-
base.config_param :url, :string, :default => 'https://api.every-sense.com:8001/'
|
14
|
-
base.desc 'login_name for EverySense API'
|
15
|
-
base.config_param :login_name, :string
|
16
|
-
base.desc 'password for EverySense API'
|
17
|
-
base.config_param :password, :string
|
18
|
-
base.config_param :limit, :integer, :default => 1000
|
19
|
-
#base.config_param :keep_alive, :integer, :default => 2
|
20
|
-
base.config_param :from, :string, :default => Time.now.iso8601
|
21
|
-
base.config_param :keep, :bool, :default => false
|
22
|
-
base.config_param :inline, :bool, :default => false
|
23
|
-
end
|
24
|
-
|
25
11
|
def start_proxy
|
26
|
-
|
12
|
+
log.debug "start everysense proxy #{@url}"
|
27
13
|
|
28
14
|
@uri = URI.parse(@url)
|
29
15
|
@https = Net::HTTP.new(@uri.host, @uri.port)
|
@@ -32,17 +18,17 @@ module Fluent
|
|
32
18
|
end
|
33
19
|
|
34
20
|
def shutdown_proxy
|
35
|
-
|
21
|
+
log.debug "shutdown_proxy #{@session_key}"
|
36
22
|
delete_session
|
37
23
|
@https.finish() if @https.active?
|
38
24
|
end
|
39
25
|
|
40
26
|
def error_handler(response, message)
|
41
27
|
if response.code != "200"
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
28
|
+
log.error error: message
|
29
|
+
log.debug "code: #{response.code}"
|
30
|
+
log.debug "message: #{response.message}"
|
31
|
+
log.debug "body: #{response.body}"
|
46
32
|
return false
|
47
33
|
end
|
48
34
|
return true
|
@@ -85,7 +71,7 @@ module Fluent
|
|
85
71
|
end
|
86
72
|
|
87
73
|
def put_message(message)
|
88
|
-
|
74
|
+
log.debug "put_message: #{message}"
|
89
75
|
put_message_res = @https.request(put_message_request(message))
|
90
76
|
error_handler(put_message_res, "put_message: '#{message}' failed.")
|
91
77
|
end
|
@@ -96,7 +82,7 @@ module Fluent
|
|
96
82
|
elsif !@recipe_id.nil?
|
97
83
|
return "/recipe_data/#{@recipe_id}.#{@format}"
|
98
84
|
else
|
99
|
-
raise ConfigError, "device_id or recipe_id must be specified."
|
85
|
+
raise Fluent::ConfigError, "device_id or recipe_id must be specified."
|
100
86
|
end
|
101
87
|
end
|
102
88
|
|
@@ -104,7 +90,7 @@ module Fluent
|
|
104
90
|
params = {
|
105
91
|
session_key: @session_key,
|
106
92
|
from: @from,
|
107
|
-
to:
|
93
|
+
to: @to,
|
108
94
|
limit: @limit
|
109
95
|
}
|
110
96
|
if !@device_id.nil?
|
@@ -117,22 +103,27 @@ module Fluent
|
|
117
103
|
end
|
118
104
|
|
119
105
|
def get_messages_request
|
106
|
+
from = Time.parse(@from)
|
107
|
+
to = from + @interval
|
108
|
+
to = Time.now if to > Time.now
|
109
|
+
@to = to.iso8601
|
120
110
|
get_messages_req = @uri + target_path
|
121
111
|
get_messages_req.query = URI.encode_www_form(get_messages_params)
|
122
|
-
|
112
|
+
log.debug "#{get_messages_req}?#{get_messages_req.query}"
|
123
113
|
# currently time window is automatically updated
|
124
|
-
|
114
|
+
#@from = Time.now.iso8601
|
115
|
+
@from = @to
|
125
116
|
get_messages_req
|
126
117
|
end
|
127
118
|
|
128
119
|
def get_messages
|
129
120
|
if !valid_session?
|
130
121
|
return nil if create_session.nil?
|
131
|
-
|
122
|
+
log.debug "session #{@session_key} created."
|
132
123
|
end
|
133
124
|
get_messages_res = @https.get(get_messages_request)
|
134
125
|
return nil if !error_handler(get_messages_res,"get_messages failed.")
|
135
|
-
|
126
|
+
log.debug "get_message: #{get_messages_res.body}"
|
136
127
|
get_messages_res.body
|
137
128
|
end
|
138
129
|
end
|
@@ -1,26 +1,29 @@
|
|
1
|
-
|
1
|
+
require 'fluent/plugin/output'
|
2
|
+
require 'fluent/event'
|
3
|
+
|
4
|
+
module Fluent::Plugin
|
2
5
|
# EverySenseFilter
|
3
6
|
# Split EverySense data into multiple output entries
|
4
7
|
# to store each datum separately
|
5
8
|
class EverySenseFilter < Filter
|
6
|
-
Plugin.register_filter('everysense', self)
|
9
|
+
Fluent::Plugin.register_filter('everysense', self)
|
7
10
|
|
8
11
|
def filter_stream(tag, es)
|
9
|
-
new_es = MultiEventStream.new
|
12
|
+
new_es = Fluent::MultiEventStream.new
|
10
13
|
es.each do |time, record|
|
11
|
-
#
|
12
|
-
|
14
|
+
# log.debug "filter_everysense: #{record}"
|
15
|
+
split_record(time, record, new_es)
|
13
16
|
end
|
14
17
|
new_es
|
15
18
|
end
|
16
19
|
|
17
20
|
private
|
18
21
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
new_es.add(
|
22
|
+
def split_record(time, record, new_es)
|
23
|
+
log.debug "split_record: #{record.inspect}"
|
24
|
+
# add each sensor as an event of fluentd
|
25
|
+
record[:device].each do |sensor|
|
26
|
+
new_es.add(Time.parse(sensor["data"]["at"]), sensor)
|
24
27
|
end
|
25
28
|
end
|
26
29
|
end
|
@@ -1,14 +1,43 @@
|
|
1
|
-
|
1
|
+
require 'fluent/plugin/input'
|
2
|
+
require 'fluent/event'
|
3
|
+
require 'fluent/time'
|
4
|
+
require 'fluent/plugin/everysense_proxy'
|
5
|
+
|
6
|
+
module Fluent::Plugin
|
2
7
|
class EverySenseInput < Input
|
3
|
-
require 'fluent/plugin/everysense_proxy'
|
4
8
|
include EverySenseProxy
|
5
9
|
|
6
|
-
Plugin.register_input('everysense', self)
|
10
|
+
Fluent::Plugin.register_input('everysense', self)
|
11
|
+
|
12
|
+
helpers :timer, :compat_parameters, :parser
|
7
13
|
|
8
|
-
|
14
|
+
desc 'EverySense API URI'
|
15
|
+
config_param :url, :string, default: 'https://api.every-sense.com:8001/'
|
16
|
+
desc 'login_name for EverySense API'
|
17
|
+
config_param :login_name, :string
|
18
|
+
desc 'password for EverySense API'
|
19
|
+
config_param :password, :string, secret: true
|
20
|
+
desc 'The tag of the event.'
|
21
|
+
config_param :tag, :string
|
22
|
+
desc 'Polling interval to get message from EverySense API (default: 60 seconds)'
|
23
|
+
config_param :polling_interval, :integer, default: 60
|
24
|
+
desc 'Device ID'
|
25
|
+
config_param :device_id, :string, default: nil
|
26
|
+
desc 'Recipe ID'
|
27
|
+
config_param :recipe_id, :string, default: nil
|
28
|
+
desc 'Maximum number of returned entries (default: 1000)'
|
29
|
+
config_param :limit, :integer, default: 1000
|
30
|
+
desc 'Start time of returned entries (default: now)'
|
31
|
+
config_param :from, :string, default: Time.now.iso8601
|
32
|
+
desc 'Time interval of returned entries (default: 300 seconds)'
|
33
|
+
config_param :interval, :integer, default: 300
|
34
|
+
desc 'EverySense API option (keep data at server or not)'
|
35
|
+
config_param :keep, :bool, default: false
|
36
|
+
desc 'EverySense API option (include farm_uuid inline or not)'
|
37
|
+
config_param :inline, :bool, default: false
|
38
|
+
desc 'EverySense API option (upload/download data format)'
|
39
|
+
config_param :format, :string, default: "json"
|
9
40
|
|
10
|
-
# currently EverySenseParser is only used by EverySense Plugin
|
11
|
-
# Parser is implemented internally.
|
12
41
|
# received message format of EverySense is as follows
|
13
42
|
#
|
14
43
|
# [
|
@@ -37,47 +66,6 @@ module Fluent
|
|
37
66
|
# }
|
38
67
|
# ]
|
39
68
|
# ]
|
40
|
-
class EverySenseParser
|
41
|
-
def initialize(format, parser)
|
42
|
-
case format
|
43
|
-
when 'json'
|
44
|
-
@parser = parser
|
45
|
-
return
|
46
|
-
when 'xml'
|
47
|
-
raise NotImplementedError, "XML parser is not implemented yet."
|
48
|
-
else
|
49
|
-
raise NotImplementedError, "XML parser is not implemented yet."
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# TODO: parser should be impelented prettier way...
|
54
|
-
# currently once parse JSON array is parsed and in map loop
|
55
|
-
# each message is re-formatted to JSON. After that it is re-parsed
|
56
|
-
# by fluent JSON parser which supports time_format etc. options...
|
57
|
-
def parse(messages)
|
58
|
-
#$log.debug messages
|
59
|
-
JSON.parse(messages).map do |message|
|
60
|
-
#$log.debug message
|
61
|
-
@parser.parse(message.to_json) do |time, record|
|
62
|
-
yield(time, record)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
desc 'Tag name assigned to inputs'
|
69
|
-
config_param :tag, :string, :default => 'everysense'
|
70
|
-
desc 'Polling interval to get message from EverySense API'
|
71
|
-
config_param :polling_interval, :integer, :default => 60
|
72
|
-
desc 'Device ID'
|
73
|
-
config_param :device_id, :string, :default => nil
|
74
|
-
desc 'Recipe ID'
|
75
|
-
config_param :recipe_id, :string, :default => nil
|
76
|
-
|
77
|
-
# Define `router` method of v0.12 to support v0.10 or earlier
|
78
|
-
unless method_defined?(:router)
|
79
|
-
define_method("router") { Fluent::Engine }
|
80
|
-
end
|
81
69
|
|
82
70
|
def configure(conf)
|
83
71
|
super
|
@@ -85,55 +73,98 @@ module Fluent
|
|
85
73
|
end
|
86
74
|
|
87
75
|
def configure_parser(conf)
|
88
|
-
|
89
|
-
|
90
|
-
@
|
76
|
+
compat_parameters_convert(conf, :parser)
|
77
|
+
parser_config = conf.elements('parse').first
|
78
|
+
@parser = parser_create(conf: parser_config)
|
91
79
|
end
|
92
80
|
|
93
81
|
def start
|
94
82
|
#raise StandardError.new if @tag.nil?
|
95
83
|
super
|
96
84
|
start_proxy
|
97
|
-
@
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
$log.error :error => e.to_s
|
105
|
-
$log.debug(e.backtrace.join("\n"))
|
106
|
-
#$log.debug_backtrace(e.backtrace)
|
107
|
-
sleep @polling_interval
|
108
|
-
end
|
85
|
+
timer_execute(:in_everysense, @polling_interval) do
|
86
|
+
begin
|
87
|
+
messages = get_messages
|
88
|
+
emit(messages) if !(messages.nil? || messages.empty?)
|
89
|
+
rescue Exception => e
|
90
|
+
log.error error: e.to_s
|
91
|
+
log.debug_backtrace(e.backtrace)
|
109
92
|
end
|
110
93
|
end
|
111
94
|
end
|
112
95
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
#$log.debug "#{time}, #{record}"
|
120
|
-
{time: time, record: record}
|
96
|
+
def parse_time(record)
|
97
|
+
if record["data"]["at"].nil?
|
98
|
+
log.debug "Since time_key field is nil, Fluent::EventTime.now is used."
|
99
|
+
Fluent::EventTime.now
|
100
|
+
else
|
101
|
+
@parser.parse_time(record["data"]["at"])
|
121
102
|
end
|
122
103
|
end
|
123
104
|
|
105
|
+
# Converted record for emission
|
106
|
+
# [
|
107
|
+
# {"farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
108
|
+
# "device":
|
109
|
+
# [
|
110
|
+
# {
|
111
|
+
# "farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
112
|
+
# "sensor_name": "collection_data_1",
|
113
|
+
# "data_class_name": "AirTemperature",
|
114
|
+
# "data": {
|
115
|
+
# "at": "2016-05-12 21:38:52 UTC",
|
116
|
+
# "memo": null,
|
117
|
+
# "value": 23,
|
118
|
+
# "unit": "degree Celsius"
|
119
|
+
# }
|
120
|
+
# },
|
121
|
+
# {
|
122
|
+
# "farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
123
|
+
# "sensor_name": "collection_data_2",
|
124
|
+
# "data_class_name": "AirHygrometer",
|
125
|
+
# "data": {
|
126
|
+
# "at": "2016-05-12 21:38:52 UTC",
|
127
|
+
# "memo": null,
|
128
|
+
# "value": 30,
|
129
|
+
# "unit": "%RH"
|
130
|
+
# }
|
131
|
+
# }
|
132
|
+
# ]
|
133
|
+
# }
|
134
|
+
# ]
|
124
135
|
def emit(messages)
|
125
136
|
begin
|
126
|
-
parse(messages)
|
127
|
-
|
137
|
+
time, record = @parser.parse(messages) do |time, record|
|
138
|
+
[time, record]
|
139
|
+
end
|
140
|
+
#log.debug "time: #{time.inspect}"
|
141
|
+
#log.debug "record: #{record.inspect}"
|
142
|
+
if record.is_a?(Array) && record[0].is_a?(Array) # Multiple devices case
|
143
|
+
mes = Fluent::MultiEventStream.new
|
144
|
+
record.each do |single_record|
|
145
|
+
# use timestamp of the first sensor (single_record[0])
|
146
|
+
mes.add(parse_time(single_record[0]), {
|
147
|
+
farm_uuid: single_record[0]["farm_uuid"],
|
148
|
+
device: single_record
|
149
|
+
})
|
150
|
+
end
|
151
|
+
router.emit_stream(@tag, mes)
|
152
|
+
elsif record.is_a?(Array) && record[0].is_a?(Hash) # Single device case
|
153
|
+
# use timestamp of the first sensor (single_record[0])
|
154
|
+
router.emit(@tag, parse_time(record[0]), {
|
155
|
+
farm_uuid: record[0]["farm_uuid"],
|
156
|
+
device: record
|
157
|
+
})
|
158
|
+
else # The other case
|
159
|
+
raise Fluent::Plugin::Parser::ParserError, "Unexpected input format."
|
128
160
|
end
|
129
161
|
rescue Exception => e
|
130
|
-
|
131
|
-
|
162
|
+
log.error error: e.to_s
|
163
|
+
log.debug_backtrace(e.backtrace)
|
132
164
|
end
|
133
165
|
end
|
134
166
|
|
135
167
|
def shutdown
|
136
|
-
@proxy_thread.kill
|
137
168
|
shutdown_proxy
|
138
169
|
super
|
139
170
|
end
|
@@ -1,22 +1,33 @@
|
|
1
|
-
|
1
|
+
require 'fluent/plugin/output'
|
2
|
+
require 'fluent/event'
|
3
|
+
require 'fluent/time'
|
4
|
+
require 'fluent/plugin/everysense_proxy'
|
5
|
+
|
6
|
+
module Fluent::Plugin
|
2
7
|
# EverySenseOutput
|
3
8
|
# output data to EverySense server
|
4
9
|
# this module assumes the input format follows everysense output specification
|
5
|
-
class EverySenseOutput <
|
6
|
-
require 'fluent/plugin/everysense_proxy'
|
10
|
+
class EverySenseOutput < Output
|
7
11
|
include EverySenseProxy
|
8
12
|
|
9
|
-
Plugin.register_output('everysense', self)
|
13
|
+
Fluent::Plugin.register_output('everysense', self)
|
14
|
+
|
15
|
+
helpers :compat_parameters, :formatter, :inject
|
10
16
|
|
11
17
|
# config_param defines a parameter. You can refer a parameter via @path instance variable
|
12
18
|
# Without :default, a parameter is required.
|
19
|
+
|
20
|
+
desc 'EverySense API URI'
|
21
|
+
config_param :url, :string, default: 'https://api.every-sense.com:8001/'
|
22
|
+
desc 'login_name for EverySense API'
|
23
|
+
config_param :login_name, :string
|
24
|
+
desc 'password for EverySense API'
|
25
|
+
config_param :password, :string, secret: true
|
13
26
|
desc 'Device ID'
|
14
27
|
config_param :device_id, :string
|
15
28
|
desc 'Flush interval to put message to EverySense API'
|
16
|
-
config_param :flush_interval, :integer, :
|
17
|
-
|
18
|
-
desc 'Aggregation type'
|
19
|
-
config_param :aggr_type, :string, :default => "none"
|
29
|
+
config_param :flush_interval, :integer, default: 30
|
30
|
+
config_param :format, :string, default: "json"
|
20
31
|
# <sensor> is mandatory option
|
21
32
|
# an example configuraton is shown below
|
22
33
|
#
|
@@ -26,21 +37,21 @@ module Fluent
|
|
26
37
|
# type_of_value Integer
|
27
38
|
# </sensor>
|
28
39
|
# <sensor>
|
29
|
-
# input_name
|
40
|
+
# input_name collection_data_2
|
30
41
|
# output_name FESTIVAL_Test1_Sensor2
|
31
42
|
# type_of_value Integer
|
32
43
|
# </sensor>
|
33
|
-
config_section :sensor, required: true, multi: true do
|
44
|
+
config_section :sensor, param_name: :sensors, required: true, multi: true do
|
34
45
|
desc 'Input sensor name'
|
35
|
-
config_param :input_name, :string, :
|
46
|
+
config_param :input_name, :string, default: nil
|
36
47
|
desc 'Output sensor name'
|
37
48
|
config_param :output_name, :string
|
38
49
|
# type_of_value: "Integer", "Float", "String", etc. The same as Ruby Class name.
|
39
50
|
desc 'Type of value'
|
40
|
-
config_param :type_of_value, :string, :
|
51
|
+
config_param :type_of_value, :string, default: "Integer"
|
41
52
|
# unit: "degree Celsius", "%RH", ...
|
42
53
|
#desc 'unit'
|
43
|
-
#config_param :unit, :string, :
|
54
|
+
#config_param :unit, :string, default: nil
|
44
55
|
end
|
45
56
|
|
46
57
|
# This method is called before starting.
|
@@ -48,24 +59,19 @@ module Fluent
|
|
48
59
|
# If the configuration is invalid, raise Fluent::ConfigError.
|
49
60
|
def configure(conf)
|
50
61
|
super
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@
|
56
|
-
@
|
57
|
-
if sensor.
|
58
|
-
@
|
62
|
+
compat_parameters_convert(conf, :formatter, :inject, :buffer, default_chunk_key: "time")
|
63
|
+
formatter_config = conf.elements(name: 'format').first
|
64
|
+
@formatter = formatter_create(conf: formatter_config)
|
65
|
+
@has_buffer_section = conf.elements(name: 'buffer').size > 0
|
66
|
+
@out_sensors = {}
|
67
|
+
@sensors.each do |sensor|
|
68
|
+
if sensor.input_name.nil?
|
69
|
+
@out_sensors[sensor.output_name] = sensor
|
59
70
|
else
|
60
|
-
@
|
71
|
+
@out_sensors[sensor.input_name] = sensor
|
61
72
|
end
|
62
73
|
end
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
def configure_formatter(conf)
|
67
|
-
#@formatter = Plugin.new_formatter(@format)
|
68
|
-
#@formatter.configure(conf)
|
74
|
+
log.debug @out_sensors.inspect
|
69
75
|
end
|
70
76
|
|
71
77
|
# This method is called when starting.
|
@@ -75,13 +81,12 @@ module Fluent
|
|
75
81
|
start_proxy
|
76
82
|
end
|
77
83
|
|
78
|
-
def
|
79
|
-
|
80
|
-
[tag, time, record].to_msgpack
|
84
|
+
def prefer_buffered_processing
|
85
|
+
@has_buffer_section
|
81
86
|
end
|
82
87
|
|
83
|
-
def force_type(value)
|
84
|
-
case
|
88
|
+
def force_type(value, out_sensor)
|
89
|
+
case out_sensor.type_of_value
|
85
90
|
when "Integer"
|
86
91
|
return value.to_i
|
87
92
|
when "Float"
|
@@ -113,85 +118,119 @@ module Fluent
|
|
113
118
|
# "sensor_name":"FESTIVAL_Test1_Sensor2"
|
114
119
|
# }
|
115
120
|
# ]
|
116
|
-
def
|
121
|
+
def transform_in_sensor(in_sensor, out_sensor) # modify sensor_name
|
117
122
|
{
|
118
123
|
data: {
|
119
|
-
at: Time.parse(
|
120
|
-
unit:
|
121
|
-
value: force_type(
|
124
|
+
at: Time.parse(in_sensor["data"]["at"]),
|
125
|
+
unit: in_sensor["data"]["unit"],
|
126
|
+
value: force_type(in_sensor["data"]["value"], out_sensor)
|
122
127
|
},
|
123
|
-
sensor_name: output_name
|
128
|
+
sensor_name: out_sensor.output_name
|
124
129
|
}
|
125
130
|
end
|
126
131
|
|
127
|
-
def
|
128
|
-
@
|
132
|
+
def get_out_sensor_by_name(input_name)
|
133
|
+
@out_sensors[input_name]
|
129
134
|
end
|
130
135
|
|
131
|
-
def
|
132
|
-
@
|
136
|
+
def get_out_sensor_by_index(index)
|
137
|
+
@out_sensors[@out_sensors.keys[index]]
|
133
138
|
end
|
134
139
|
|
135
|
-
|
136
|
-
|
140
|
+
# Assumed input message format is as follows
|
141
|
+
#
|
142
|
+
# [
|
143
|
+
# [
|
144
|
+
# {
|
145
|
+
# "farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
146
|
+
# "sensor_name": "collection_data_1",
|
147
|
+
# "data_class_name": "AirTemperature",
|
148
|
+
# "data": {
|
149
|
+
# "at": "2016-05-12 21:38:52 UTC",
|
150
|
+
# "memo": null,
|
151
|
+
# "value": 23,
|
152
|
+
# "unit": "degree Celsius"
|
153
|
+
# }
|
154
|
+
# },
|
155
|
+
# {
|
156
|
+
# "farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
157
|
+
# "sensor_name": "collection_data_2",
|
158
|
+
# "data_class_name": "AirHygrometer",
|
159
|
+
# "data": {
|
160
|
+
# "at": "2016-05-12 21:38:52 UTC",
|
161
|
+
# "memo": null,
|
162
|
+
# "value": 30,
|
163
|
+
# "unit": "%RH"
|
164
|
+
# }
|
165
|
+
# }
|
166
|
+
# ]
|
167
|
+
# ]
|
168
|
+
def transform_in_device(in_device)
|
169
|
+
if in_device[0]["sensor_name"].nil?
|
137
170
|
# if first input data does not include sensor_name,
|
138
171
|
# output_names are used in the specified order.
|
139
|
-
return
|
140
|
-
if !
|
141
|
-
|
172
|
+
return in_device.map.with_index do |in_sensor, i|
|
173
|
+
if !get_out_sensor_by_index(i).nil?
|
174
|
+
transform_in_sensor(in_sensor, get_out_sensor_by_index(i))
|
175
|
+
end
|
176
|
+
end.compact
|
177
|
+
else
|
178
|
+
return in_device.map do |in_sensor|
|
179
|
+
#log.debug in_sensor["sensor_name"]
|
180
|
+
if @out_sensors.keys.include?(in_sensor["sensor_name"])
|
181
|
+
transform_in_sensor(in_sensor, get_out_sensor_by_name(in_sensor["sensor_name"]))
|
142
182
|
end
|
143
183
|
end.compact
|
144
184
|
end
|
145
|
-
device_data.map do |sensor_data|
|
146
|
-
#$log.debug sensor_data["sensor_name"]
|
147
|
-
if @sensor_hash.keys.include?(sensor_data["sensor_name"])
|
148
|
-
transform_sensor_data(sensor_data, get_output_name_by_name(sensor_data["sensor_name"]))
|
149
|
-
end
|
150
|
-
end.compact
|
151
185
|
end
|
152
186
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
187
|
+
# Emitted record inside fluentd network
|
188
|
+
# [
|
189
|
+
# {"farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
190
|
+
# "device":
|
191
|
+
# [
|
192
|
+
# {
|
193
|
+
# "farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
194
|
+
# "sensor_name": "collection_data_1",
|
195
|
+
# "data_class_name": "AirTemperature",
|
196
|
+
# "data": {
|
197
|
+
# "at": "2016-05-12 21:38:52 UTC",
|
198
|
+
# "memo": null,
|
199
|
+
# "value": 23,
|
200
|
+
# "unit": "degree Celsius"
|
201
|
+
# }
|
202
|
+
# },
|
203
|
+
# {
|
204
|
+
# "farm_uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
205
|
+
# "sensor_name": "collection_data_2",
|
206
|
+
# "data_class_name": "AirHygrometer",
|
207
|
+
# "data": {
|
208
|
+
# "at": "2016-05-12 21:38:52 UTC",
|
209
|
+
# "memo": null,
|
210
|
+
# "value": 30,
|
211
|
+
# "unit": "%RH"
|
212
|
+
# }
|
213
|
+
# }
|
214
|
+
# ]
|
215
|
+
# }
|
216
|
+
# ]
|
217
|
+
def put_event_stream(tag, es)
|
218
|
+
es = inject_values_to_event_stream(tag, es)
|
219
|
+
es.each do |time, record|
|
220
|
+
log.debug "#{tag}, #{record}"
|
221
|
+
put_message(@formatter.format(tag, time, transform_in_device(record["device"])))
|
170
222
|
end
|
171
|
-
$log.debug avg_device_data.to_json
|
172
|
-
put_message(avg_device_data.to_json)
|
173
223
|
end
|
174
224
|
|
175
|
-
def
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
def min(chunk)
|
180
|
-
# TODO
|
225
|
+
def process(tag, es)
|
226
|
+
put_event_stream(tag, es)
|
181
227
|
end
|
182
228
|
|
183
229
|
def write(chunk)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
put_message(transform_device_data(record["json"]).to_json)
|
189
|
-
end
|
190
|
-
when "avg", "max", "min"
|
191
|
-
method(@aggr_type).call(chunk)
|
192
|
-
else
|
193
|
-
raise NotImplementedError, "specified aggr_type is not implemented."
|
194
|
-
end
|
230
|
+
return if chunk.empty?
|
231
|
+
tag = chunk.metadata.tag
|
232
|
+
|
233
|
+
put_event_stream(tag, es)
|
195
234
|
end
|
196
235
|
|
197
236
|
# This method is called when shutting down.
|
@@ -9,11 +9,19 @@ https://service.every-sense.com で遊ぶためには最低限以下の用語を
|
|
9
9
|
- **ファームオーナー**: EverySenseにアップロードするデータを持っている人。デバイスのクラスを使って自分のデバイスを **ファーム** に登録できます。デバイスを登録すると **デバイスのUUID** を取得できます。なお、ここで述べているデバイスは、EverySenseサーバ上の仮想デバイスを指します。物理デバイスからのデータアップロード時に取得したデバイスのUUIDを付与することで、物理的なデバイスとEverySenseサーバ上の仮想デバイスを対応づけます。
|
10
10
|
- **レストランオーナー**: EverySenseからデータをダウンロードして活用する人。自分が欲しいデータの条件を **レシピ** として登録し、**レシピのUUID** を取得します。**レシピ** は検索条件で指定した **ファームオーナー** に送ることができます。検索条件はファームオーナーのキーワードから指定できます。
|
11
11
|
- **デバイスベンダー**: 物理デバイスの開発元や物理デバイスにとっても詳しいデバイス所有者。デバイスのクラスを定義する権限をもちます。
|
12
|
-
- **レシピ**: **レストランオーナー** から **ファームオーナー**
|
12
|
+
- **レシピ**: **レストランオーナー** から **ファームオーナー** に送られる欲しいデータの条件。条件にマッチするデータを出力するデバイス群(**ファーム**)を保持する**ファームオーナー**に**オーダー**として送信されます。**ファームオーナー** は送られてきたオーダーに対してデータを送信しても良いかどうか確認し、承認処理を行います。必要であれば、さらにレストランオーナー側にも承認プロセスを入れることも可能です。承認されたレシピでは承認した **ファームオーナー** のデバイス(仮想デバイス)またはファーム(複数のデバイスを含むことが可能)に送られたデータを取得することができます。
|
13
13
|
- **デバイスのクラス**: スマートフォンのようにデバイスには複数のセンサが搭載されていると想定されています。デバイスにどのような精度のセンサが搭載されているのか、例えば製品ごとに事前にクラスとして登録しておくことができます。**ファームオーナー** は保持している物理デバイスに対応するクラスを選択して、EverySenseに登録することになります。
|
14
14
|
- **デバイスとセンサ**: **デバイスのクラス** を定義する際には、物理デバイスに接続された **センサ** を登録する必要があります。1つのデバイスに対して複数の **センサ** が取り付けられることが想定されています。**センサ** は無線接続されたものも含むため、センサごとに設置箇所を登録できます。登録時に指定した **センサ** の名称はデータアップロード時に利用するのでメモしておきましょう。
|
15
|
+
- **ファーム**: **ファームオーナー**は保持している**デバイス**をEverySenseサーバに登録する必要があります。登録時には物理デバイスに対応する仮想デバイスを**デバイスのクラス**を指定して生成します。さらに登録した仮想デバイスから**オーダー**の対象となる**ファーム**を作成します。複数のデバイスを組み合わせて**ファーム**を作成することもできます。
|
16
|
+
|
17
|
+
![ファームへのデバイス登録とレシピからのオーダー生成](https://raw.githubusercontent.com/toyokazu/fluent-plugin-everysense/master/tutorial/ja/images/every-sense-overview01.png "ファームへのデバイス登録とレシピからのオーダー生成")
|
18
|
+
|
19
|
+
図1: ファームへのデバイス登録とレシピからのオーダー生成
|
20
|
+
|
21
|
+
![オーダーの承認とセンサデータの送信](https://raw.githubusercontent.com/toyokazu/fluent-plugin-everysense/master/tutorial/ja/images/every-sense-overview02.png "オーダーの承認とセンサデータの送信")
|
22
|
+
|
23
|
+
図2: オーダーの承認とセンサデータの送信
|
15
24
|
|
16
|
-
![EverySense基礎用語の概説図](https://raw.githubusercontent.com/toyokazu/fluent-plugin-everysense/master/tutorial/ja/images/every-sense-overview.png "EverySense基礎用語の概説図")
|
17
25
|
|
18
26
|
## アカウントの登録
|
19
27
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-everysense
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Toyokazu Akiyama
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -16,42 +16,56 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.14.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.14.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
33
|
+
version: '1.14'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
40
|
+
version: '1.14'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '12.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '12.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: test-unit
|
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'
|
55
69
|
description: Fluent Input/Output plugin for EverySense Framework
|
56
70
|
email:
|
57
71
|
- toyokazu@gmail.com
|
@@ -60,6 +74,7 @@ extensions: []
|
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- ".gitignore"
|
77
|
+
- CHANGELOG.md
|
63
78
|
- Gemfile
|
64
79
|
- LICENSE
|
65
80
|
- README.md
|
@@ -75,7 +90,7 @@ files:
|
|
75
90
|
- tutorial/ja/json_over_http_api_tutorial.md
|
76
91
|
homepage: https://github.com/toyokazu/fluent-plugin-everysense
|
77
92
|
licenses:
|
78
|
-
-
|
93
|
+
- Apache License Version 2.0
|
79
94
|
metadata: {}
|
80
95
|
post_install_message:
|
81
96
|
rdoc_options: []
|
@@ -85,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
100
|
requirements:
|
86
101
|
- - ">="
|
87
102
|
- !ruby/object:Gem::Version
|
88
|
-
version: 2.
|
103
|
+
version: 2.2.0
|
89
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
105
|
requirements:
|
91
106
|
- - ">="
|
@@ -93,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
108
|
version: '0'
|
94
109
|
requirements: []
|
95
110
|
rubyforge_project:
|
96
|
-
rubygems_version: 2.
|
111
|
+
rubygems_version: 2.6.8
|
97
112
|
signing_key:
|
98
113
|
specification_version: 4
|
99
114
|
summary: Fluent Input/Output plugin for EverySense Framework
|