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 +4 -4
- data/README.md +4 -2
- data/bin/midea-ac +162 -0
- data/lib/client.rb +4 -2
- data/lib/commands/command.rb +2 -0
- data/lib/commands/request_status.rb +2 -0
- data/lib/commands/set.rb +2 -0
- data/lib/device.rb +2 -0
- data/lib/midea_air_condition.rb +2 -0
- data/lib/packet_builder.rb +2 -2
- data/lib/security.rb +8 -2
- data/lib/version.rb +3 -1
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a7001346211ad18fb8e3f5fc2383024c6c4429a
|
4
|
+
data.tar.gz: 47926522126546b46d17849330ea9141ded166e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90dcdb2e2342380400c117bd18dd57f8438882c38fd2e6dafea002bdcea0f66fc12eae9cae5e4ebe2095b8224d8a025ef17a503cbef113ee1f1dc6cfb9086991
|
7
|
+
data.tar.gz: 49e96c6572017ac06b166a569e26bb07475292f93432ae418cd88bbe48c5795b5f8d66519d586362844f1d0f7bbcc4330e9a38cb33f09bccff89cb7f794fca9c
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
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
|
-
|
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,
|
data/bin/midea-ac
ADDED
@@ -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
|
data/lib/client.rb
CHANGED
@@ -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'
|
11
|
+
SERVER_URL = 'https://mapp.appsmb.com/v1'
|
10
12
|
CLIENT_TYPE = 1 # Android
|
11
13
|
FORMAT = 2 # JSON
|
12
|
-
LANGUAGE = 'en_US'
|
14
|
+
LANGUAGE = 'en_US'
|
13
15
|
|
14
16
|
attr_accessor :debug
|
15
17
|
|
data/lib/commands/command.rb
CHANGED
data/lib/commands/set.rb
CHANGED
data/lib/device.rb
CHANGED
data/lib/midea_air_condition.rb
CHANGED
data/lib/packet_builder.rb
CHANGED
@@ -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
|
|
data/lib/security.rb
CHANGED
@@ -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
|
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.
|
109
|
+
final.unpack1('H*')
|
106
110
|
end
|
111
|
+
# rubocop:enable Metrics/AbcSize
|
112
|
+
# rubocop:enable Metrics/MethodLength
|
107
113
|
end
|
108
114
|
end
|
data/lib/version.rb
CHANGED
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.
|
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-
|
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.
|
170
|
+
version: '2.4'
|
155
171
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
172
|
requirements:
|
157
173
|
- - ">="
|