mqttsrvc 0.6.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/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: []
|