midea-air-condition 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d5cebc189d6087144eae46746756a45bee886a4d
4
- data.tar.gz: 92f8832bee21623bd30922649d606b7078a40814
3
+ metadata.gz: 7a7001346211ad18fb8e3f5fc2383024c6c4429a
4
+ data.tar.gz: 47926522126546b46d17849330ea9141ded166e4
5
5
  SHA512:
6
- metadata.gz: 172c3d5bc3f92ece38bf1fb34773431fe5d0de85f249e56ae5bb1dda1b157c41050e644cf73e033312e94fcea736dd35fb977ca07909a858a110dad26a3bb704
7
- data.tar.gz: f34c1e00a63474e3096e400d0eb32fafcc8552f498196fa4bbe4c3ee95a76c6708a3a3cc5148213d05d12ddd338464ef172bd54d52b16c5695ebbf8f4b2c9e47
6
+ metadata.gz: 90dcdb2e2342380400c117bd18dd57f8438882c38fd2e6dafea002bdcea0f66fc12eae9cae5e4ebe2095b8224d8a025ef17a503cbef113ee1f1dc6cfb9086991
7
+ data.tar.gz: 49e96c6572017ac06b166a569e26bb07475292f93432ae418cd88bbe48c5795b5f8d66519d586362844f1d0f7bbcc4330e9a38cb33f09bccff89cb7f794fca9c
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
- ### How to use
1
+ ## How to use
2
2
 
3
3
  The API key is `3742e9e5842d4ad59c2db887e12449f9` if you extract
4
4
  it from their `.apk` package. But I'm not sure.
5
5
 
6
+ ### Example:
7
+
6
8
  ```
7
9
  require_relative 'lib/midea_air_condition'
8
10
 
@@ -99,7 +101,7 @@ device = MideaAirCondition::Device.new(response)
99
101
  puts " Target temperature is #{device.temperature} celsius."
100
102
  ```
101
103
 
102
- ### CRC Table + base64
104
+ ## CRC Table + base64
103
105
 
104
106
  I tried to generate the crc8 table, but I can't.
105
107
  The table itself is a very long one,
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'thor'
5
+ require 'yaml'
6
+ require 'midea_air_condition'
7
+
8
+ # Command Line Interface
9
+ class MideaCLI < Thor
10
+ CONFIG_PATH = File.expand_path('~/.midea/config')
11
+
12
+ desc 'configure', 'Configure client'
13
+ def configure
14
+ credentials = {}
15
+ credentials['email'] = ask('Email:')
16
+ credentials['password'] = ask('Password:')
17
+ credentials['app_key'] = ask('App key:')
18
+ config_dir = File.dirname(CONFIG_PATH)
19
+ Dir.mkdir(config_dir) unless Dir.exist?(config_dir)
20
+ File.write(CONFIG_PATH, credentials.to_yaml)
21
+ end
22
+
23
+ desc 'list', 'List devices'
24
+ def list
25
+ devices = client.appliance_list
26
+ head = [
27
+ { name: 'ID', id: 'id' },
28
+ { name: 'Name', id: 'name' },
29
+ { name: 'Type', id: 'type' },
30
+ { name: 'Online', id: 'onlineStatus' },
31
+ { name: 'Active', id: 'activeStatus' }
32
+ ]
33
+ print_table(devices, head)
34
+ end
35
+
36
+ # rubocop:disable Metrics/AbcSize
37
+ # rubocop:disable Metrics/MethodLength
38
+ desc 'get ID', 'Gets device\'s information'
39
+ def get(id)
40
+ command = MideaAirCondition::Command::RequestStatus.new
41
+ device = send_command(id, command)
42
+ puts "Device is turned #{print_on_off(device.power_status)}."
43
+ puts "Target temperature: #{device.temperature} celsius"
44
+ puts "Indoor temperature: #{device.indoor_temperature} celsius"
45
+ puts "Outdoor temperature: #{device.outdoor_temperature} celsius"
46
+ puts "Mode: #{device.mode_human}"
47
+ puts "Fan speed: #{device.fan_speed}"
48
+ puts "TimerOn is #{print_not(device.on_timer[:status])} active."
49
+ puts " at: #{device.on_timer_human}" if device.on_timer[:status]
50
+ puts "TimerOff is #{print_not(device.off_timer[:status])} active."
51
+ puts " at: #{device.off_timer_human}" if device.off_timer[:status]
52
+ puts "Eco mode is #{print_on_off(device.eco)}."
53
+ end
54
+ # rubocop:enable Metrics/MethodLength
55
+
56
+ desc 'set ID', 'Sets device\'s power, temperature and fan speed'
57
+ method_option :power, required: false, type: :string, aliases: '-p', desc: 'Power'
58
+ method_option :target_temperature, required: false, type: :numeric, aliases: '-t', desc: 'Target temperature'
59
+ method_option :fan_speed, required: false, type: :numeric, aliases: '-f', desc: 'Fan speed'
60
+ def set(id)
61
+ command = MideaAirCondition::Command::Set.new
62
+ command.turn_off if options[:power] == 'off'
63
+ command.turn_on if options[:power] == 'on'
64
+ command.temperature(options[:target_temperature]) if options[:target_temperature]
65
+ command.fan_speed(options[:fan_speed]) if options[:fan_speed]
66
+ send_command(id, command)
67
+ end
68
+ # rubocop:enable Metrics/AbcSize
69
+
70
+ # rubocop:disable Metrics/BlockLength
71
+ no_commands do
72
+ def credentials
73
+ return @credentials if @credentials
74
+ if (@credentials = credentials_from_env)
75
+ @credentials
76
+ elsif (@credentials = credentials_from_yaml)
77
+ @credentials
78
+ else
79
+ raise Thor::Error, 'No credentials found'
80
+ end
81
+ end
82
+
83
+ def credentials_from_env
84
+ env_variables = %w[MIDEA_AC_EMAIL MIDEA_AC_PASSWORD MIDEA_AC_APP_KEY]
85
+ env_prefix = 'MIDEA_AC_'
86
+ return nil unless env_variables.all? { |k| ENV.key? k }
87
+ Hash[
88
+ ENV.select { |k, _| env_variables.include?(k) }
89
+ .map { |k, v| [k.gsub(env_prefix, '').downcase, v] }
90
+ ]
91
+ end
92
+
93
+ def credentials_from_yaml
94
+ return nil unless File.file?(CONFIG_PATH)
95
+ YAML.load_file(CONFIG_PATH)
96
+ end
97
+
98
+ def client
99
+ return @client if @client
100
+ @client = MideaAirCondition::Client.new(
101
+ credentials['email'],
102
+ credentials['password'],
103
+ app_key: credentials['app_key']
104
+ )
105
+ @client.login
106
+ @client
107
+ end
108
+
109
+ def send_command(id, command)
110
+ builder = client.new_packet_builder
111
+ builder.add_command(command)
112
+ begin
113
+ response = client.appliance_transparent_send(id, builder.finalize)
114
+ rescue RuntimeError => e
115
+ raise Thor::Error, 'The device is offline.' if e.message == 'the appliance is off line.'
116
+ raise e
117
+ end
118
+
119
+ MideaAirCondition::Device.new(response)
120
+ end
121
+
122
+ def print_on_off(value)
123
+ value ? 'on' : 'off'
124
+ end
125
+
126
+ def print_not(value)
127
+ value ? '' : 'not'
128
+ end
129
+
130
+ def print_table(values, head)
131
+ calculate_table_width!(values, head)
132
+ print_line(head)
133
+ print_head(head)
134
+ print_line(head)
135
+ values.each { |row| print_row(head, row) }
136
+ print_line(head)
137
+ end
138
+
139
+ def calculate_table_width!(values, head)
140
+ head.each do |title|
141
+ title[:lenght] = (values.map { |value| value[title[:id]].to_s.length } + [title[:name].length]).max
142
+ end
143
+ end
144
+
145
+ def print_line(head)
146
+ puts '-' * (head.map { |e| e[:lenght] + 3 }.reduce(&:+) + 1)
147
+ end
148
+
149
+ def print_head(head)
150
+ columns = head.map { |e| e[:name].ljust(e[:lenght], ' ') }
151
+ puts '| ' + columns.join(' | ') + ' |'
152
+ end
153
+
154
+ def print_row(head, row)
155
+ columns = head.map { |e| row[e[:id]].to_s.ljust(e[:lenght], ' ') }
156
+ puts '| ' + columns.join(' | ') + ' |'
157
+ end
158
+ end
159
+ # rubocop:enable Metrics/BlockLength
160
+ end
161
+
162
+ MideaCLI.start
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest/sha2'
2
4
  require 'json'
3
5
  require 'net/http'
@@ -6,10 +8,10 @@ require 'openssl'
6
8
  module MideaAirCondition
7
9
  # Client for Midea AC server
8
10
  class Client
9
- SERVER_URL = 'https://mapp.appsmb.com/v1'.freeze
11
+ SERVER_URL = 'https://mapp.appsmb.com/v1'
10
12
  CLIENT_TYPE = 1 # Android
11
13
  FORMAT = 2 # JSON
12
- LANGUAGE = 'en_US'.freeze
14
+ LANGUAGE = 'en_US'
13
15
 
14
16
  attr_accessor :debug
15
17
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MideaAirCondition
2
4
  module Command
3
5
  # Base Command class
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MideaAirCondition
2
4
  module Command
3
5
  # Request status of a device
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MideaAirCondition
2
4
  module Command
3
5
  # Request status of a device
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MideaAirCondition
2
4
  # Device representation (now only for status parsing)
3
5
  class Device
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'client'
2
4
  require_relative 'device'
3
5
  require_relative 'packet_builder'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MideaAirCondition
2
4
  # This is where we build our packets
3
5
  class PacketBuilder
@@ -30,8 +32,6 @@ module MideaAirCondition
30
32
  @packet += [0]
31
33
  @packet[0x04] = @packet.length
32
34
 
33
- p @packet
34
-
35
35
  @packet
36
36
  end
37
37
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module MideaAirCondition
@@ -48,7 +50,7 @@ module MideaAirCondition
48
50
  data.each do |m|
49
51
  k = crc_value ^ m
50
52
  k -= 256 if k > 256
51
- k += 256 if k < 0
53
+ k += 256 if k.negative?
52
54
  crc_value = @crc8_854_table[k]
53
55
  end
54
56
 
@@ -60,6 +62,8 @@ module MideaAirCondition
60
62
  255 - sum_value % 256 + 1
61
63
  end
62
64
 
65
+ # rubocop:disable Metrics/AbcSize
66
+ # rubocop:disable Metrics/MethodLength
63
67
  def aes_decrypt(data, key)
64
68
  aes = OpenSSL::Cipher.new('aes128')
65
69
  aes.decrypt
@@ -102,7 +106,9 @@ module MideaAirCondition
102
106
  final += aes.update(b) + aes.final
103
107
  end
104
108
 
105
- final.unpack('H*').first
109
+ final.unpack1('H*')
106
110
  end
111
+ # rubocop:enable Metrics/AbcSize
112
+ # rubocop:enable Metrics/MethodLength
107
113
  end
108
114
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MideaAirCondition
2
4
  # Current version to publish
3
5
  # Gemspec also uses this constant
4
- VERSION = '0.0.1'.freeze
6
+ VERSION = '0.0.2'
5
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: midea-air-condition
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Balazs Nadasdi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-25 00:00:00.000000000 Z
11
+ date: 2018-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.0.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.20.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.20.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -124,12 +138,14 @@ dependencies:
124
138
  version: '3.0'
125
139
  description: API client for Midea AC systems
126
140
  email: balazs.nadasdi@cheppers.com
127
- executables: []
141
+ executables:
142
+ - midea-ac
128
143
  extensions: []
129
144
  extra_rdoc_files: []
130
145
  files:
131
146
  - LICENSE
132
147
  - README.md
148
+ - bin/midea-ac
133
149
  - lib/client.rb
134
150
  - lib/commands/command.rb
135
151
  - lib/commands/request_status.rb
@@ -151,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
167
  requirements:
152
168
  - - ">="
153
169
  - !ruby/object:Gem::Version
154
- version: '2.0'
170
+ version: '2.4'
155
171
  required_rubygems_version: !ruby/object:Gem::Requirement
156
172
  requirements:
157
173
  - - ">="