From 2970c80b83f8e501256bb80883a04a866bbf3e60 Mon Sep 17 00:00:00 2001 From: imi415 Date: Mon, 25 Jul 2022 23:46:21 +0800 Subject: [PATCH] Added memory cache. --- .env.example | 2 + .github/workflows/build-publish-image.yaml | 30 ++++++++++++ Gemfile | 1 + Gemfile.lock | 2 + src/server.rb | 55 +++++++++++++++------- 5 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/build-publish-image.yaml diff --git a/.env.example b/.env.example index 629ad6c..a7b78e5 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ MOJI_APPCODE="YOUR_MOJI_APP_CODE" +CACHE_TTL="1800" + IOT_MQTT_HOST="YOUR_MQTT_HOST" IOT_MQTT_PORT="1883" diff --git a/.github/workflows/build-publish-image.yaml b/.github/workflows/build-publish-image.yaml new file mode 100644 index 0000000..884c530 --- /dev/null +++ b/.github/workflows/build-publish-image.yaml @@ -0,0 +1,30 @@ +name: "Build and publish image" +on: + push: + branches: ["master"] + +jobs: + build: + runs-on: "ubuntu-latest" + steps: + - name: "Checkout repository" + uses: "actions/checkout@v2" + - name: "Login with Github packages" + uses: "docker/login-action@v1" + with: + registry: "ghcr.io" + username: "${{ github.repository_owner }}" + password: "${{ secrets.GITHUB_TOKEN }}" + - name: "Set up buildx" + uses: "docker/setup-buildx-action@v1" + - name: "Extract branch name" + shell: "bash" + run: "echo \"##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})\"" + id: "extract_branch" + - name: "Build and push image" + uses: "docker/build-push-action@v2" + with: + context: "./" + file: "./Dockerfile" + push: true + tags: "ghcr.io/${{ github.repository_owner }}/iot-weather:${{ steps.extract_branch.outputs.branch }}" diff --git a/Gemfile b/Gemfile index 071c672..5871560 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ source 'https://rubygems.org' gem 'cbor', '~> 0.5.9.6' gem 'dotenv', '~>2.7' +gem 'lru_redux', '~>1.1' gem 'moji_weather', github: 'imi415/moji_weather', branch: 'master' gem 'mqtt', '~>0.5' diff --git a/Gemfile.lock b/Gemfile.lock index 3c6e276..88d0e1c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -48,6 +48,7 @@ GEM rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) + lru_redux (1.1.0) mqtt (0.5.0) multipart-post (2.2.3) nokogiri (1.13.8-x86_64-linux) @@ -103,6 +104,7 @@ PLATFORMS DEPENDENCIES cbor (~> 0.5.9.6) dotenv (~> 2.7) + lru_redux (~> 1.1) moji_weather! mqtt (~> 0.5) rubocop diff --git a/src/server.rb b/src/server.rb index aba28a1..5209ddb 100644 --- a/src/server.rb +++ b/src/server.rb @@ -5,9 +5,10 @@ require 'bundler' Bundler.require Dotenv.load -Dotenv.require_keys('MOJI_APPCODE', 'IOT_MQTT_HOST', 'IOT_MQTT_PORT', 'IOT_MQTT_SSL') +Dotenv.require_keys('MOJI_APPCODE', 'CACHE_TTL', 'IOT_MQTT_HOST', 'IOT_MQTT_PORT', 'IOT_MQTT_SSL') wxapi = MojiWeather::Api::RestClient.new(app_code: ENV['MOJI_APPCODE']) +cache = LruRedux::TTL::Cache.new(100, ENV['CACHE_TTL'].to_i) def extract_device_id(topic) devid = %r{iot/weather/(.*)/request}.match(topic) @@ -19,6 +20,17 @@ def extract_device_id(topic) end end +def extract_req_type(type) + case type + when 'condition' + MojiWeather::Api::ApiType::CONDITION + when 'aqi' + MojiWeather::Api::ApiType::AQI + when 'forecast24' + MojiWeather::Api::ApiType::FORECAST_24HRS + end +end + begin client = MQTT::Client.new @@ -34,49 +46,60 @@ begin client.connect + puts "[#{Time.now}] client [#{client}] connected..." + 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" + puts "[#{Time.now}][#{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 + wx_cond = extract_req_type(dev_req['type']) + + # Not a valid type + next if wx_cond.nil? 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" + puts "[#{Time.now}][#{dev_id}] not a valid request" next end - # Request external service for weather information - api_resp = wxapi.query(wx_cond, req_params) + # Check cache + api_resp = cache["cache_#{req_params}"] + + # Cache missed.. + if api_resp.nil? + puts "[#{Time.now}][#{dev_id}] cache missed" + # Request external service for weather information + api_resp = wxapi.query(wx_cond, req_params) + + # Update cache entry + cache["cache_#{req_params}"] = api_resp + else + puts "[#{Time.now}][#{dev_id}] cache hit" + end # Encode CBOR object + resp = api_resp.to_cbor # Publish to response topic. - resp = api_resp.to_cbor - puts "[#{dev_id}] -> #{resp.length}B" + puts "[#{Time.now}][#{dev_id}] -> #{resp.length}B" + # Send response client.publish("iot/weather/#{dev_id}/response", resp) end end rescue SystemExit, Interrupt - puts "Interrupt caught, client [#{client}] disconnect." + puts "[#{Time.now}] Interrupt caught, client [#{client}] disconnect." client.disconnect rescue StandardError => e p e