mqttsrvc 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/README.md +11 -0
- data/bin/mqttsrvc +6 -0
- data/etc/credentials.json +15 -0
- data/etc/logger.yml +18 -0
- data/etc/mqtt.yml +34 -0
- data/lib/app.rb +25 -0
- data/lib/config.rb +19 -0
- data/lib/credentials.rb +25 -0
- data/lib/mqtt_srvc_logger.rb +80 -0
- data/lib/mqttsrvc.rb +136 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 22bdb9e7497a2abc7ae4cf66afc4524d804f4703419db3d3d85619077f50aa09
|
4
|
+
data.tar.gz: 6755e5808f1d2ee5ade3fc1ea3ccd7bb73f8eb56977cce102e6f083f13304de0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 92e6bed956f55124e1cb470bfde0feb0e07cac3c10bdc3fcec6454f32690a6532293addafe8f4d1495cb3a3dc34945482bfdcaebc29833f442f3b9a68130c20f
|
7
|
+
data.tar.gz: 3a5684ada6e6d1d6879ecbdadce3e0fa6922a95a677c3399071e98d2358dcffe1fac63a37ad9f5085b57384b5e500b45f95d51bf8ca01e9209c60bcbc408e5b9
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# MqttSrvc
|
2
|
+
|
3
|
+
MqttSrvc makes it easy to implement a new, MQTT-integrated service in Ruby. It handles:
|
4
|
+
|
5
|
+
- loading YAML config
|
6
|
+
- connecting to a broker
|
7
|
+
- subscribing to configured topics
|
8
|
+
- logging to a topic
|
9
|
+
- adding supplementary fields to each published message:
|
10
|
+
+ client_id
|
11
|
+
+ ip_address
|
data/bin/mqttsrvc
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"_public_key": "711abc041141040248de76d821b8de45074966eaebcef47ce7ccc5e5c13b3a2f",
|
3
|
+
"development": {
|
4
|
+
"mqtt": {
|
5
|
+
"api_key": "EJ[1:U03yLpdanoN3d5Sm0DjXn7NwLdOvsHetC/Fh0PpLzmI=:7v4AnlOf77hw5Jh3aE3WEvcUEl6sQWFF:HIUVVaF62X4Owq7Z0U5Ri6FKhl6xyfaHIcg=]",
|
6
|
+
"api_secret": "EJ[1:U03yLpdanoN3d5Sm0DjXn7NwLdOvsHetC/Fh0PpLzmI=:/1cGum3JgAPqcPo3/H+lkStVMNmTWkty:8XKNRGsia/rfDS769WvDVMQw6+5QJH6eJQCqQr3Nkqqo8W5W2Svyg8kjqp+ndT1J]"
|
7
|
+
}
|
8
|
+
},
|
9
|
+
"production": {
|
10
|
+
"mqtt": {
|
11
|
+
"api_key": "EJ[1:U03yLpdanoN3d5Sm0DjXn7NwLdOvsHetC/Fh0PpLzmI=:pRFNwbtaSbiQ39KH4TYkOlzWUVUPCCF3:a7IMgniMI5dwSyymORiuw1VBxKQWKK3D7Eg=]",
|
12
|
+
"api_secret": "EJ[1:U03yLpdanoN3d5Sm0DjXn7NwLdOvsHetC/Fh0PpLzmI=:jiydWqPIb+s6Yf9kcEvinAnDiCWfapka:JzUtb5upPNe5M37oWuJGVBgV17MmZdqZB9ETt8+kY/ugv33CzmMZiQWGlSOxd64R]"
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
data/etc/logger.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
default: &default
|
2
|
+
loggers:
|
3
|
+
-
|
4
|
+
level: debug
|
5
|
+
path: STDOUT
|
6
|
+
-
|
7
|
+
level: info
|
8
|
+
path: log/mqttsrvc.log
|
9
|
+
size: 52428800
|
10
|
+
count: 5
|
11
|
+
development:
|
12
|
+
<<: *default
|
13
|
+
test:
|
14
|
+
<<: *default
|
15
|
+
staging:
|
16
|
+
<<: *default
|
17
|
+
production:
|
18
|
+
<<: *default
|
data/etc/mqtt.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
default: &default
|
2
|
+
client_id: mqttsrvc_0
|
3
|
+
host: localhost
|
4
|
+
port: 1883
|
5
|
+
ssl: false
|
6
|
+
keep_alive: 60
|
7
|
+
implicit_hash_key: "_msg"
|
8
|
+
topics:
|
9
|
+
sub:
|
10
|
+
- "/home/hello/in"
|
11
|
+
- "/home/shadowbox/cmd"
|
12
|
+
boot: "/home/boot"
|
13
|
+
lwt: "/home/lwt"
|
14
|
+
log: "/home/log"
|
15
|
+
pub:
|
16
|
+
- "/home/hello/out"
|
17
|
+
ping: "/home/ping"
|
18
|
+
pong: "/home/pong"
|
19
|
+
qos: 2
|
20
|
+
retain: 0
|
21
|
+
keepalive: 15
|
22
|
+
client:
|
23
|
+
omit_retained: true
|
24
|
+
development:
|
25
|
+
<<: *default
|
26
|
+
test:
|
27
|
+
<<: *default
|
28
|
+
staging:
|
29
|
+
<<: *default
|
30
|
+
production:
|
31
|
+
<<: *default
|
32
|
+
host: 'thngs.rmtly.com'
|
33
|
+
port: 8883
|
34
|
+
ssl: true
|
data/lib/app.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mqttsrvc'
|
4
|
+
|
5
|
+
# sample MqttSrvc implementation
|
6
|
+
class HelloMqttSrvc < MqttSrvc
|
7
|
+
def initialize
|
8
|
+
super(__dir__)
|
9
|
+
|
10
|
+
publish(@config.topics.pub.first, 'HelloWorld!')
|
11
|
+
end
|
12
|
+
|
13
|
+
def mqtt_receive(topic, msg, _msg_hash)
|
14
|
+
log_msg = "Received a message on #{topic}: #{msg}"
|
15
|
+
logger.debug(log_msg)
|
16
|
+
logger.data(log_msg)
|
17
|
+
logger.info(log_msg)
|
18
|
+
logger.warn(log_msg)
|
19
|
+
logger.error(log_msg)
|
20
|
+
logger.error_bold(log_msg)
|
21
|
+
publish(@config.topics.pub.first, @credentials['api_key'])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
HelloMqttSrvc.new.run if $PROGRAM_NAME == __FILE__
|
data/lib/config.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'recursive_open_struct'
|
3
|
+
|
4
|
+
class Config
|
5
|
+
def self.for(dir:, config_type:, config_extension: 'yml')
|
6
|
+
config_file_for_type = config_file(dir, config_type) + '.' + config_extension
|
7
|
+
|
8
|
+
puts "Loading config for environment #{environment} from '#{config_file_for_type}'..."
|
9
|
+
RecursiveOpenStruct.new(YAML.load_file(config_file_for_type)[environment])
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.config_file(dir, config_type)
|
13
|
+
File.expand_path File.join(dir, '..', 'etc', config_type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.environment
|
17
|
+
ENV['ENV'] || 'development'
|
18
|
+
end
|
19
|
+
end
|
data/lib/credentials.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Credentials
|
4
|
+
CREDENTIALS_FILE = 'credentials.json'.freeze
|
5
|
+
|
6
|
+
def self.for(dir:, credentials_type:)
|
7
|
+
puts "Loading #{credentials_type} credentials for environment #{environment} from '#{credentials_file(dir)}'..."
|
8
|
+
load(dir)[environment][credentials_type]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.credentials_file(dir)
|
12
|
+
File.join(dir, '..', 'etc', CREDENTIALS_FILE)
|
13
|
+
end
|
14
|
+
private_class_method :credentials_file
|
15
|
+
|
16
|
+
def self.environment
|
17
|
+
ENV['ENV'] || 'development'
|
18
|
+
end
|
19
|
+
private_class_method :environment
|
20
|
+
|
21
|
+
def self.load(dir)
|
22
|
+
JSON.parse(`ejson decrypt #{credentials_file(dir)}`)
|
23
|
+
end
|
24
|
+
private_class_method :load
|
25
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/bin/false
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'colorize'
|
5
|
+
require 'recursive_open_struct'
|
6
|
+
|
7
|
+
class MqttSrvcLogger
|
8
|
+
def initialize(dir)
|
9
|
+
config = Config.for(dir: dir, config_type: 'logger')['loggers']
|
10
|
+
|
11
|
+
@loggers = {}
|
12
|
+
@logger_formatter = proc do |severity, datetime, _progname, msg|
|
13
|
+
"%s %-6s %s\n" % [datetime, severity, msg]
|
14
|
+
end
|
15
|
+
|
16
|
+
config.each do |logger_config|
|
17
|
+
logger_config = RecursiveOpenStruct.new(logger_config)
|
18
|
+
|
19
|
+
@loggers[logger_config.path] = configured_logger(logger_config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def debug(msg)
|
24
|
+
each_logger { |logger| logger.debug(msg.light_blue) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def info(msg)
|
28
|
+
each_logger { |logger| logger.info(msg.green) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def data(msg)
|
32
|
+
each_logger { |logger| logger.info(msg.magenta) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def warn(msg)
|
36
|
+
each_logger { |logger| logger.warn(msg.yellow) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def error(msg)
|
40
|
+
each_logger { |logger| logger.error(msg.red) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def error_bold(msg)
|
44
|
+
each_logger { |logger| logger.error(msg.red.bold) }
|
45
|
+
end
|
46
|
+
|
47
|
+
alias fatal error_bold
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def each_logger
|
52
|
+
@loggers.each do |_key, logger|
|
53
|
+
yield logger
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def configured_logger(logger_config)
|
58
|
+
if logger_config.path == 'STDOUT'
|
59
|
+
STDOUT.sync = true
|
60
|
+
logger = Logger.new(STDOUT)
|
61
|
+
else
|
62
|
+
create_log_dir(File.dirname(logger_config.path))
|
63
|
+
|
64
|
+
logger = Logger.new(
|
65
|
+
logger_config.path,
|
66
|
+
logger_config.count,
|
67
|
+
logger_config.size
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
logger.level = logger_config.level if logger_config.level
|
72
|
+
logger.formatter = @logger_formatter
|
73
|
+
|
74
|
+
logger
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_log_dir(dir)
|
78
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
79
|
+
end
|
80
|
+
end
|
data/lib/mqttsrvc.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mqtt'
|
4
|
+
require 'json'
|
5
|
+
require_relative 'config'
|
6
|
+
require_relative 'credentials'
|
7
|
+
require_relative 'mqtt_srvc_logger'
|
8
|
+
|
9
|
+
# framework for building ruby-based MQTT services
|
10
|
+
class MqttSrvc
|
11
|
+
CONFIG_TYPE = 'mqtt'.freeze
|
12
|
+
|
13
|
+
attr_reader :logger
|
14
|
+
|
15
|
+
def initialize(dir)
|
16
|
+
@config = Config.for(dir: dir, config_type: CONFIG_TYPE)
|
17
|
+
@credentials = Credentials.for(dir: dir, credentials_type: 'mqtt')
|
18
|
+
|
19
|
+
puts 'Setting up logger...'
|
20
|
+
@logger = MqttSrvcLogger.new(dir)
|
21
|
+
@logger.info("Logger configured.")
|
22
|
+
|
23
|
+
@logger.info('Creating MQTT client...')
|
24
|
+
@mqtt_client = MQTT::Client.connect(
|
25
|
+
host: @config.host,
|
26
|
+
port: @config.port,
|
27
|
+
ssl: @config.ssl,
|
28
|
+
keep_alive: @config.keep_alive,
|
29
|
+
username: @credentials['api_key'],
|
30
|
+
password: @credentials['api_secret']
|
31
|
+
)
|
32
|
+
@logger.info("Client connected to #{@config.host}.")
|
33
|
+
|
34
|
+
subscribe_to_topics
|
35
|
+
subscribe_to_ping
|
36
|
+
end
|
37
|
+
|
38
|
+
def run
|
39
|
+
@run = true
|
40
|
+
do_loop
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop
|
44
|
+
@run = false
|
45
|
+
end
|
46
|
+
|
47
|
+
def publish(topic, payload, retain = false)
|
48
|
+
payload = { @config.implicit_hash_key => payload } if payload.class.to_s == "".class.to_s
|
49
|
+
add_client_id(payload)
|
50
|
+
payload = payload.to_json
|
51
|
+
@logger.info('PUB ' + topic + ' | ' + payload.to_s)
|
52
|
+
@mqtt_client.publish(topic, payload, retain: retain)
|
53
|
+
end
|
54
|
+
|
55
|
+
def ping_topic
|
56
|
+
@ping_topic ||= @config.topics.ping + '/' + @credentials['api_key']
|
57
|
+
end
|
58
|
+
|
59
|
+
def pong_topic
|
60
|
+
@pong_topic ||= @config.topics.pong + '/' + @credentials['api_key']
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def subscribe_to_topics
|
66
|
+
topics = @config.topics.sub
|
67
|
+
if topics.nil? || topics.empty?
|
68
|
+
@logger.info('No topics to which to subscribe.')
|
69
|
+
return
|
70
|
+
end
|
71
|
+
topics.each do |topic|
|
72
|
+
@logger.debug("Subscribing to #{topic}...")
|
73
|
+
subscribe(topic)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def subscribe_to_ping
|
78
|
+
@logger.debug("Subscribing to #{ping_topic}...")
|
79
|
+
subscribe(ping_topic)
|
80
|
+
end
|
81
|
+
|
82
|
+
def subscribe(topic)
|
83
|
+
@logger.info('SUB ' + topic)
|
84
|
+
@mqtt_client.subscribe(topic)
|
85
|
+
end
|
86
|
+
|
87
|
+
def do_loop
|
88
|
+
while @run
|
89
|
+
begin
|
90
|
+
@mqtt_client.get(nil, @config.client) do |topic, msg|
|
91
|
+
@logger.debug 'RCV ' + topic + ' | ' + msg.to_s
|
92
|
+
msg_hash = decode_message(msg)
|
93
|
+
mqtt_receive_shim(topic, msg, msg_hash)
|
94
|
+
end
|
95
|
+
rescue SystemExit, Interrupt
|
96
|
+
raise
|
97
|
+
rescue StandardError => e
|
98
|
+
@logger.error_bold e.message
|
99
|
+
@logger.error ' ' + e.backtrace.join($/ + ' ')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
rescue Interrupt => e
|
103
|
+
@logger.info '[INT] Exiting...'
|
104
|
+
begin
|
105
|
+
@mqtt_client.disconnect
|
106
|
+
rescue Interrupt
|
107
|
+
@logger.info '[INT] Aborting...'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def decode_message(msg)
|
112
|
+
begin
|
113
|
+
msg_hash = JSON.parse(msg)
|
114
|
+
msg_hash = nil unless msg_hash.class == Hash
|
115
|
+
rescue JSON::ParserError
|
116
|
+
msg_hash = {@config.implicit_hash_key => msg}
|
117
|
+
end
|
118
|
+
msg_hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def mqtt_receive_shim(topic, msg, msg_hash)
|
122
|
+
@logger.info "#{topic} #{msg}"
|
123
|
+
if topic == ping_topic
|
124
|
+
@logger.debug('Handling ping...')
|
125
|
+
publish(pong_topic, state: true)
|
126
|
+
else
|
127
|
+
mqtt_receive(topic, msg, msg_hash)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def add_client_id(payload)
|
132
|
+
payload[:clientid] = @credentials['api_key']
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
MqttSrvc.new(__dir__).run if $PROGRAM_NAME == __FILE__
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mqttsrvc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- nick@rmtly.com
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.8'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.8.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.8'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.8.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: mqtt
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.5.0
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0.5'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.5.0
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0.5'
|
53
|
+
description:
|
54
|
+
email: nick@rmtly.com
|
55
|
+
executables:
|
56
|
+
- mqttsrvc
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- Gemfile
|
61
|
+
- README.md
|
62
|
+
- bin/mqttsrvc
|
63
|
+
- etc/credentials.json
|
64
|
+
- etc/logger.yml
|
65
|
+
- etc/mqtt.yml
|
66
|
+
- lib/app.rb
|
67
|
+
- lib/config.rb
|
68
|
+
- lib/credentials.rb
|
69
|
+
- lib/mqtt_srvc_logger.rb
|
70
|
+
- lib/mqttsrvc.rb
|
71
|
+
homepage: https://rmtly.com
|
72
|
+
licenses:
|
73
|
+
- Nonstandard
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubygems_version: 3.0.3
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: mqttsrvc provides a framework for MQTT-based services
|
94
|
+
test_files: []
|