solectrus/senec

View on GitHub
lib/senec/local/request.rb

Summary

Maintainability
A
3 hrs
Test Coverage
A
100%
require_relative 'value'
require_relative 'constants'

module Senec
  module Local
    class Request
      def initialize(connection:, body: BASIC_REQUEST, state_names: nil)
        @connection = connection
        @body = body
        @state_names = state_names
      end

      attr_reader :connection, :body, :state_names

      def perform!
        parsed_response
        true
      end

      def house_power
        get('ENERGY', 'GUI_HOUSE_POW')
      end

      def inverter_power
        get('ENERGY', 'GUI_INVERTER_POWER')
      end

      def mpp_power
        get('PV1', 'MPP_POWER')
      end

      def power_ratio
        get('PV1', 'POWER_RATIO')
      end

      def bat_power
        get('ENERGY', 'GUI_BAT_DATA_POWER')
      end

      def bat_fuel_charge
        get('ENERGY', 'GUI_BAT_DATA_FUEL_CHARGE')
      end

      def bat_charge_current
        get('ENERGY', 'GUI_BAT_DATA_CURRENT')
      end

      def bat_voltage
        get('ENERGY', 'GUI_BAT_DATA_VOLTAGE')
      end

      def grid_power
        get('ENERGY', 'GUI_GRID_POW')
      end

      def wallbox_charge_power
        get('WALLBOX', 'APPARENT_CHARGING_POWER')
      end

      def case_temp
        get('TEMPMEASURE', 'CASE_TEMP')
      end

      def application_version
        get('WIZARD', 'APPLICATION_VERSION')
      end

      def current_state_code
        get('ENERGY', 'STAT_STATE')
      end

      def current_state_name
        throw RuntimeError, 'No state names provided!' unless state_names

        state_names[current_state_code]
      end

      def measure_time
        web_time = get('RTC', 'WEB_TIME')
        utc_offset = get('RTC', 'UTC_OFFSET')

        web_time - (utc_offset * 60)
      end

      def response_duration
        raw_response.env[:duration]
      end

      def get(*keys)
        return unless parsed_response

        value = parsed_response.dig(*keys)

        if value.is_a?(Array)
          value.map do |v|
            Value.new(v).decoded
          end
        elsif value
          Value.new(value).decoded
        else
          raise Error, "Value missing for #{keys.join('.')}"
        end
      rescue Senec::Local::DecodingError => e
        raise Error, "Decoding failed for #{keys.join('.')}: #{e.message}"
      end

      def path
        '/lala.cgi'
      end

      private

      def parsed_response
        @parsed_response ||= JSON.parse(raw_response.body)
      end

      def raw_response
        @raw_response ||= begin
          response = connection.post(path, request_body, request_header)
          raise Error, response.status unless response.success?

          response
        end
      end

      def request_body
        JSON.generate(body)
      end

      def request_header
        {
          'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',
          'Accept' => 'application/json, text/javascript, */*; q=0.01'
        }
      end
    end
  end
end