mqttsrvc 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []