From 68b832529bcc57772245d88de32690262e87c670 Mon Sep 17 00:00:00 2001 From: imi415 Date: Mon, 25 Jul 2022 00:50:42 +0800 Subject: [PATCH] Initial commit. --- .env.example | 9 ++++ .gitignore | 3 ++ .rubocop.yml | 0 Gemfile | 11 +++++ Gemfile.lock | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/client.rb | 37 +++++++++++++++++ src/server.rb | 83 +++++++++++++++++++++++++++++++++++++ 7 files changed, 255 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .rubocop.yml create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 src/client.rb create mode 100644 src/server.rb diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..629ad6c --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +MOJI_APPCODE="YOUR_MOJI_APP_CODE" + +IOT_MQTT_HOST="YOUR_MQTT_HOST" +IOT_MQTT_PORT="1883" + +IOT_MQTT_SSL="true" +IOT_MQTT_SSL_CERT_FILE="path/to/client.crt" +IOT_MQTT_SSL_KEY_FILE="path/to/client.key" +IOT_MQTT_SSL_CA_FILE="path/to/ca.crt" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf26a77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor/bundle +/.env +/ssl \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..e69de29 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..071c672 --- /dev/null +++ b/Gemfile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'cbor', '~> 0.5.9.6' +gem 'dotenv', '~>2.7' +gem 'moji_weather', github: 'imi415/moji_weather', branch: 'master' +gem 'mqtt', '~>0.5' + +gem 'rubocop', group: 'development' +gem 'solargraph', group: 'development' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..3c6e276 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,112 @@ +GIT + remote: https://github.com/imi415/moji_weather.git + revision: 5a08ea60b4f00e98496a38c9a2bcbec6aea6776e + branch: master + specs: + moji_weather (0.0.1) + faraday (~> 1.4) + faraday_middleware (~> 1.0) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + backport (1.2.0) + benchmark (0.2.0) + cbor (0.5.9.6) + diff-lcs (1.5.0) + dotenv (2.7.6) + e2mmap (0.1.0) + faraday (1.10.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + jaro_winkler (1.5.4) + json (2.6.2) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + mqtt (0.5.0) + multipart-post (2.2.3) + nokogiri (1.13.8-x86_64-linux) + racc (~> 1.4) + parallel (1.22.1) + parser (3.1.2.0) + ast (~> 2.4.1) + racc (1.6.0) + rainbow (3.1.1) + regexp_parser (2.5.0) + reverse_markdown (2.1.1) + nokogiri + rexml (3.2.5) + rubocop (1.32.0) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.1.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.19.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.19.1) + parser (>= 3.1.1.0) + ruby-progressbar (1.11.0) + ruby2_keywords (0.0.5) + solargraph (0.45.0) + backport (~> 1.2) + benchmark + bundler (>= 1.17.2) + diff-lcs (~> 1.4) + e2mmap + jaro_winkler (~> 1.5) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + parser (~> 3.0) + reverse_markdown (>= 1.0.5, < 3) + rubocop (>= 0.52) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + thor (1.2.1) + tilt (2.0.11) + unicode-display_width (2.2.0) + webrick (1.7.0) + yard (0.9.28) + webrick (~> 1.7.0) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + cbor (~> 0.5.9.6) + dotenv (~> 2.7) + moji_weather! + mqtt (~> 0.5) + rubocop + solargraph + +BUNDLED WITH + 2.3.3 diff --git a/src/client.rb b/src/client.rb new file mode 100644 index 0000000..cbcf8a4 --- /dev/null +++ b/src/client.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'bundler' + +Bundler.require + +Dotenv.load +Dotenv.require_keys('MOJI_APPCODE', 'IOT_MQTT_HOST', 'IOT_MQTT_PORT', 'IOT_MQTT_SSL') + +begin + client = MQTT::Client.new + + client.host = ENV['IOT_MQTT_HOST'] + client.port = ENV['IOT_MQTT_PORT'].to_i + + unless ENV['IOT_MQTT_SSL'].nil? || ENV['IOT_MQTT_SSL'] == 'false' || ENV['IOT_MQTT_SSL'] == 'no' + client.ssl = true + client.cert_file = ENV['IOT_MQTT_SSL_CERT_FILE'] + client.key_file = ENV['IOT_MQTT_SSL_KEY_FILE'] + client.ca_file = ENV['IOT_MQTT_SSL_CA_FILE'] + end + + client.connect + + client.subscribe('iot/weather/testclient/response') + + client.publish('iot/weather/testclient/request', { type: 'condition', city_id: 10 }.to_cbor) + + _, payload = client.get + p CBOR.decode(payload) + +rescue SystemExit, Interrupt + puts "Interrupt caught, client [#{client}] disconnect." + client.disconnect +rescue StandardError => e + p e +end diff --git a/src/server.rb b/src/server.rb new file mode 100644 index 0000000..aba28a1 --- /dev/null +++ b/src/server.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'bundler' + +Bundler.require + +Dotenv.load +Dotenv.require_keys('MOJI_APPCODE', 'IOT_MQTT_HOST', 'IOT_MQTT_PORT', 'IOT_MQTT_SSL') + +wxapi = MojiWeather::Api::RestClient.new(app_code: ENV['MOJI_APPCODE']) + +def extract_device_id(topic) + devid = %r{iot/weather/(.*)/request}.match(topic) + + if devid.nil? + nil + else + devid[1] + end +end + +begin + client = MQTT::Client.new + + client.host = ENV['IOT_MQTT_HOST'] + client.port = ENV['IOT_MQTT_PORT'].to_i + + unless ENV['IOT_MQTT_SSL'].nil? || ENV['IOT_MQTT_SSL'] == 'false' || ENV['IOT_MQTT_SSL'] == 'no' + client.ssl = true + client.cert_file = ENV['IOT_MQTT_SSL_CERT_FILE'] + client.key_file = ENV['IOT_MQTT_SSL_KEY_FILE'] + client.ca_file = ENV['IOT_MQTT_SSL_CA_FILE'] + end + + client.connect + + client.subscribe('iot/weather/#') + + client.get do |topic, payload| + # this method also checks if this is from a request topic. + dev_id = extract_device_id(topic) + unless dev_id.nil? + puts "[#{dev_id}] <- #{payload.length}B" + + # decode CBOR object, retrieve request. + dev_req = CBOR.decode(payload) + + wx_cond = case dev_req['type'] + when 'condition' + MojiWeather::Api::ApiType::CONDITION + when 'aqi' + MojiWeather::Api::ApiType::AQI + when 'forecast24' + MojiWeather::Api::ApiType::FORECAST_24HRS + end + + req_params = if !dev_req['city_id'].nil? + { city_id: dev_req['city_id'] } + elsif !dev_req['location'].nil? + { location: { lat: dev_req['location']['lat'], lon: dev_req['location']['lon'] } } + else + puts "[#{dev_id}] not a valid request" + next + end + + # Request external service for weather information + api_resp = wxapi.query(wx_cond, req_params) + + # Encode CBOR object + + # Publish to response topic. + resp = api_resp.to_cbor + puts "[#{dev_id}] -> #{resp.length}B" + + client.publish("iot/weather/#{dev_id}/response", resp) + end + end +rescue SystemExit, Interrupt + puts "Interrupt caught, client [#{client}] disconnect." + client.disconnect +rescue StandardError => e + p e +end