midea-air-condition 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="