woox 0.0.1

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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +52 -0
  3. data/bin/woox +247 -0
  4. metadata +94 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 17c9cdba29244d47abb7332e08eeb06f001683195407f00e943035a66fbc3e60
4
+ data.tar.gz: 165c578c0f8883a141bf406cb43d44c67bb3509505dcb98d9e2aabd2f001a784
5
+ SHA512:
6
+ metadata.gz: 65889379e745a0732c13c621ac17c93e87a68ae7a59ddd443d703b0590ae9705171cb247d84e5fa01f69a72f05d7cbe3edc90326226a950dc0a9b9168a924cdb
7
+ data.tar.gz: 6b7a4b98fe2867ce99c66783a4b76915d3a56e1de585283f459a97576f21cdc855e7b08674eb8427843e6bc63a29b19d213cef95f345e89c1d4608528ade21f0
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Woox
2
+
3
+ A low-overhead, highly optimized local area network (LAN) library for communicating directly with Woox Smart Home hardware (Plugs, Bulbs, and Strips) using Tuya v3.3 socket engines. Carefully structured to enforce zero-object-allocation cipher cycles, maximizing execution performance on lower-end embedded microprocessors.
4
+
5
+ ## 🤝 Project Origin & Credits
6
+ This library was authored by Jan Svoboda (`escpos`) in collaboration with an interactive physical hardware logging sandbox loop and a Google AI assistant. This engineering process isolated and resolved the strict 16-byte cryptographic resetting quirks of the Tuya protocol.
7
+
8
+ ## 🔑 How to Retrieve Your Device ID and Local Key
9
+
10
+ ### Method A: The QR Code Script Approach (Easiest / No Developer Account)
11
+ You can completely skip creating cloud developer projects by using public reverse-engineered client tools:
12
+ 1. Run the official interactive reverse-extraction terminal utility:
13
+ ```bash
14
+ npx @tuyapi/cli wizard
15
+ ```
16
+ 2. Enter the standard application User ID code from your mobile **Smart Life** or **Tuya Smart** application profile.
17
+ 3. Scan the terminal's generated QR code with your mobile app's scanner. It will immediately dump every connected hardware `Device ID` and `Local Key` string.
18
+
19
+ ### Method B: The Tuya Cloud Explorer Approach (Official Manual Route)
20
+ 1. Register a trial workspace on the [Tuya Developer Portal](https://tuya.com).
21
+ 2. Navigate to **Cloud -> Link Tuya App** and scan the link vector with your mobile app.
22
+ 3. Head to **Cloud -> API Explorer -> Smart Home Device System -> Device Management -> Query Device Details**.
23
+ 4. Provide your hardware tracking strings to retrieve your permanent, matching 16-character `local_key`.
24
+
25
+ ## 💻 Installation
26
+ Add this line to your project's `Gemfile`:
27
+ ```ruby
28
+ gem 'woox'
29
+ ```
30
+
31
+ And then execute:
32
+ ```bash
33
+ \$ bundle install
34
+ ```
35
+
36
+ ## 🛠 Command Line Interface (CLI) Use
37
+ Once installed, use the global shell hook to toggle state values or parse metrics straight from your terminal terminal:
38
+
39
+ ```bash
40
+ # General Syntax: woox <plug|bulb> <ip> <device_id> <local_key> <action> [value]
41
+
42
+ # Controlling a Smart Plug:
43
+ \$ woox plug 192.168.1.50 <device_id> <local_key> on
44
+ \$ woox plug 192.168.1.50 <device_id> <local_key> status
45
+
46
+ # Controlling a Smart Bulb/LED Strip:
47
+ \$ woox bulb 192.168.1.60 <device_id> <local_key> brightness 300
48
+ \$ woox bulb 192.168.1.60 <device_id> <local_key> color green
49
+ ```
50
+
51
+ ## 🔴 Comprehensive Programming Examples
52
+ For an exhaustive, runnable breakdown of all available capabilities (including dimming loops and sequential multi-DP unbundled tracking controls), see the `examples/all_use_cases.rb` script inside the repository directory.
data/bin/woox ADDED
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Robust path interception: pulls from local lib tree if testing locally
5
+ local_lib = File.expand_path("../lib", __dir__)
6
+ if File.directory?(local_lib)
7
+ $LOAD_PATH.unshift(local_lib)
8
+ end
9
+
10
+ begin
11
+ require "woox"
12
+ rescue LoadError => e
13
+ puts "Error: Cannot load the Woox library module framework."
14
+ puts e.message
15
+ exit 1
16
+ end
17
+
18
+ require "io/console"
19
+ require "json"
20
+
21
+ # FIXED: Re-engineered using string definitions to prevent markdown rendering bugs from stripping the array values
22
+ COLOR_PRESETS = {
23
+ "red" => "255,0,0".split(",").map(&:to_i),
24
+ "green" => "0,255,0".split(",").map(&:to_i),
25
+ "blue" => "0,0,255".split(",").map(&:to_i),
26
+ "yellow" => "255,255,0".split(",").map(&:to_i),
27
+ "magenta" => "255,0,255".split(",").map(&:to_i),
28
+ "cyan" => "0,255,255".split(",").map(&:to_i),
29
+ "orange" => "255,165,0".split(",").map(&:to_i),
30
+ "purple" => "128,0,128".split(",").map(&:to_i),
31
+ "pink" => "255,192,203".split(",").map(&:to_i),
32
+ "white" => "255,255,255".split(",").map(&:to_i)
33
+ }
34
+
35
+ # Helper method to transform standard RGB representations into Tuya v3.3 12-character HHHHSSSSVVVV profiles
36
+ def convert_rgb_to_tuya_hex(r, g, b, targeted_brightness = nil)
37
+ r_n = r / 255.0
38
+ g_n = g / 255.0
39
+ b_n = b / 255.0
40
+
41
+ max_val = [r_n, g_n, b_n].max
42
+ min_val = [r_n, g_n, b_n].min
43
+ delta = max_val - min_val
44
+
45
+ # Calculate Hue (Scaled 0-360)
46
+ h = 0
47
+ if delta > 0
48
+ if max_val == r_n
49
+ h = (60 * ((g_n - b_n) / delta) + 360) % 360
50
+ elsif max_val == g_n
51
+ h = (60 * ((b_n - r_n) / delta) + 120) % 360
52
+ elsif max_val == b_n
53
+ h = (60 * ((r_n - g_n) / delta) + 240) % 360
54
+ end
55
+ end
56
+
57
+ # Calculate Saturation (Scaled 0-1000)
58
+ s = max_val == 0 ? 0 : (delta / max_val) * 1000
59
+
60
+ # If a specific brightness level is provided, use that instead of maximizing to 1000 based on RGB layout
61
+ v = targeted_brightness ? targeted_brightness : (max_val * 1000)
62
+
63
+ # Convert safely to hex strings padded precisely to 4 characters
64
+ sprintf("%04x%04x%04x", h.round, s.round, v.round)
65
+ end
66
+
67
+ # Utility to break down flexible input formats (presets, comma structures, or hex values) into raw RGB values
68
+ def parse_color_input(input)
69
+ cleaned = input.to_s.strip.downcase
70
+
71
+ if COLOR_PRESETS.has_key?(cleaned)
72
+ return COLOR_PRESETS[cleaned]
73
+ end
74
+
75
+ if cleaned.include?(",")
76
+ parts = cleaned.split(",").map { |p| p.to_i }
77
+ return parts if parts.length == 3
78
+ end
79
+
80
+ hex_match = cleaned.gsub("#", "")
81
+ if hex_match.length == 6 && hex_match =~ /\A[0-9a-f]{6}\z/
82
+ r = hex_match[0..1].to_i(16)
83
+ g = hex_match[2..3].to_i(16)
84
+ b = hex_match[4..5].to_i(16)
85
+ return [r, g, b]
86
+ end
87
+
88
+ nil
89
+ end
90
+
91
+ # Enforce a strict 16-byte length boundary safety check for cryptographic keys
92
+ def sanitize_key(raw_key)
93
+ cleaned = raw_key.to_s.strip
94
+ if cleaned.bytesize > 16
95
+ cleaned[0...16]
96
+ elsif cleaned.bytesize < 16
97
+ cleaned.ljust(16, "\x00")
98
+ else
99
+ cleaned
100
+ end
101
+ end
102
+
103
+ # Executed when running the interactive CLI interface
104
+ def run_interactive_menu(device, type)
105
+ loop do
106
+ puts "\n--- Woox Interactive Controller (#{(type == 'bulb' ? 'Smart Bulb' : 'Smart Plug')}) ---"
107
+ puts " Power Relay ON"
108
+ puts " Power Relay OFF"
109
+ puts " [s] Query Telemetry Status Matrix"
110
+ if type == "bulb"
111
+ puts " [b] Set White Brightness Level (10-1000)"
112
+ puts " [c] Modify Chromatic RGB Color Space"
113
+ end
114
+ puts " [q] Quit CLI Interface"
115
+ print "Select action: "
116
+ choice = IO.console.gets.strip.downcase
117
+
118
+ case choice
119
+ when "1", "on"
120
+ puts device.turn_on ? "✅ Success: Device turned ON" : "❌ Error: Command failed"
121
+ when "2", "off"
122
+ puts device.turn_off ? "✅ Success: Device turned OFF" : "❌ Error: Command failed"
123
+ when "s", "status"
124
+ status = device.get_status
125
+ if status
126
+ puts "✅ Current Data Points (dps): #{status['dps']}"
127
+ else
128
+ puts "❌ Error: Failed to retrieve status payload."
129
+ end
130
+ when "b", "brightness"
131
+ if type == "bulb"
132
+ print "Enter brightness level (10-1000): "
133
+ val = IO.console.gets.strip.to_i
134
+ puts device.set_brightness(val) ? "✅ Brightness adjusted" : "❌ Command failed"
135
+ end
136
+ when "c", "color"
137
+ if type == "bulb"
138
+ print "Enter color (e.g., 'red', 'ff0000', or '0,255,0'): "
139
+ input = IO.console.gets.strip
140
+ rgb = parse_color_input(input)
141
+ if rgb
142
+ # Read the current status to preserve the active brightness level
143
+ status = device.get_status
144
+ active_brightness = nil
145
+
146
+ if status && status["dps"]
147
+ if status["dps"]["21"] == "colour" && status["dps"]["24"]
148
+ # Extract the last 4 hex characters (VVVV) from the existing color code
149
+ active_brightness = status["dps"]["24"][-4..-1].to_i(16)
150
+ elsif status["dps"]["22"]
151
+ # Fallback to white mode brightness if switching from white to color
152
+ active_brightness = status["dps"]["22"].to_i
153
+ end
154
+ end
155
+
156
+ tuya_hex = convert_rgb_to_tuya_hex(rgb[0], rgb[1], rgb[2], active_brightness)
157
+ puts device.set_dps("21" => "colour", "24" => tuya_hex) ? "✅ Color shifted to RGB #{rgb.inspect}" : "❌ Command failed"
158
+ else
159
+ puts "❌ Error: Unknown color pattern entry."
160
+ end
161
+ end
162
+ when "q", "quit"
163
+ puts "Exiting script runner."
164
+ break
165
+ else
166
+ puts "❌ Error: Invalid selection menu choice."
167
+ end
168
+ end
169
+ end
170
+
171
+ # ==============================================================================
172
+ # MAIN ROUTING BLOCK
173
+ # ==============================================================================
174
+
175
+ if ARGV.empty? || ARGV == ["-i"] || ARGV == ["--interactive"]
176
+ puts "Woox Local Engine Configuration Setup Wizard"
177
+ puts "============================================="
178
+ print "Device Target Type (plug/bulb): "; type = IO.console.gets.strip.downcase
179
+ print "Device LAN Network IP Address : "; ip = IO.console.gets.strip
180
+ print "Alphanumeric Hardware Device ID: "; device_id = IO.console.gets.strip
181
+ print "Permanent 16-Character Key : "; raw_key = IO.console.gets.strip
182
+
183
+ key = sanitize_key(raw_key)
184
+ device = (type == "bulb") ? Woox::LocalBulb.new(ip, device_id, key) : Woox::LocalPlug.new(ip, device_id, key)
185
+ run_interactive_menu(device, type)
186
+ exit 0
187
+ end
188
+
189
+ if ARGV.length < 5
190
+ puts "Usage (Automation Mode): woox <plug|bulb> <ip> <device_id> <local_key> <action> [value]"
191
+ puts "Usage (Interactive Mode): woox --interactive"
192
+ exit 1
193
+ end
194
+
195
+ type, ip, device_id, local_key, action, value = ARGV[0..5]
196
+ key = sanitize_key(local_key)
197
+ device = (type == "bulb") ? Woox::LocalBulb.new(ip, device_id, key) : Woox::LocalPlug.new(ip, device_id, key)
198
+
199
+ case action.to_s.downcase
200
+ when "1", "on"
201
+ exit(device.turn_on ? 0 : 1)
202
+ when "2", "off"
203
+ exit(device.turn_off ? 0 : 1)
204
+ when "s", "status"
205
+ status = device.get_status
206
+ if status
207
+ puts status["dps"].to_json
208
+ exit 0
209
+ else
210
+ puts "Error"
211
+ exit 1
212
+ end
213
+ when "brightness", "bright"
214
+ if type == "bulb"
215
+ exit(device.set_brightness(value.to_i) ? 0 : 1)
216
+ else
217
+ puts "Error: Brightness parameter adjustments are only supported on bulb hardware profiles."
218
+ exit 1
219
+ end
220
+ when "color", "colour"
221
+ if type == "bulb"
222
+ rgb = parse_color_input(value)
223
+ if rgb
224
+ status = device.get_status
225
+ active_brightness = nil
226
+ if status && status["dps"]
227
+ if status["dps"]["21"] == "colour" && status["dps"]["24"]
228
+ active_brightness = status["dps"]["24"][-4..-1].to_i(16)
229
+ elsif status["dps"]["22"]
230
+ active_brightness = status["dps"]["22"].to_i
231
+ end
232
+ end
233
+
234
+ tuya_hex = convert_rgb_to_tuya_hex(rgb[0], rgb[1], rgb[2], active_brightness)
235
+ exit(device.set_dps("21" => "colour", "24" => tuya_hex) ? 0 : 1)
236
+ else
237
+ puts "Error: Unknown color matrix format."
238
+ exit 1
239
+ end
240
+ else
241
+ puts "Error: Color parameters are only supported on bulb hardware profiles."
242
+ exit 1
243
+ end
244
+ else
245
+ puts "Unknown action: #{action}"
246
+ exit 1
247
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: woox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jan Svoboda
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: Engineered via an exhaustive human-AI hardware testing session, this
56
+ gem provides direct local Tuya v3.3 socket communication configurations for Woox
57
+ smart plugs, avoiding all external cloud routing bottlenecks.
58
+ email:
59
+ - jan@mluv.cz
60
+ executables:
61
+ - woox
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - README.md
66
+ - bin/woox
67
+ homepage: https://github.com/svoboda-jan/woox
68
+ licenses:
69
+ - MIT
70
+ metadata:
71
+ homepage_uri: https://github.com/svoboda-jan/woox
72
+ source_code_uri: https://github.com/svoboda-jan/woox
73
+ cooperation_credits: Developed via interactive runtime pair-programming between
74
+ a human engineer running active physical test benches and a Google AI assistant.
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 1.9.2
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 3.1.6
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: A lightweight local network Ruby driver for Woox Smart Home devices.
94
+ test_files: []