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.
@@ -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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ gem 'mqtt'
8
+ gem 'colorize'
9
+ gem 'recursive-open-struct'
10
+ gem 'ejson'
@@ -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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mqttsrvc'
4
+
5
+ mqttsrvc = MqttSrvc.new(__dir__)
6
+ mqttsrvc.run
@@ -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
+ }
@@ -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
@@ -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
@@ -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__
@@ -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
@@ -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
@@ -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: []