esphome 0.1.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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/exe/esphome-monitor +157 -0
  3. metadata +99 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1caa16ccd3799a2a3139eb844270d03908ad2a90e039545fe22965e44e2a4631
4
+ data.tar.gz: a2112bfaf0021a412a49850b755656f9ae4690ed1433729c30274c88ec2d4396
5
+ SHA512:
6
+ metadata.gz: 86a427ef2f9ce500abd913bc435cd10e8951f539122723699606f840f3cd9bc9f7e0babdf7b0572c1137e144c5bb392277bbfb61777bdd755789a59e8e709b73
7
+ data.tar.gz: 1625c81170abda661eaacf6938d5e54dbcab261b21397d5da870276be09c046432b16c8596580c41dbdf11e83e68629b2272b308bcbbc32bc4194104488a009e
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "curses"
5
+ require "esphome"
6
+ require "time"
7
+
8
+ ANSI_COLOR_MAP = {
9
+ 30 => Curses::COLOR_BLACK,
10
+ 31 => Curses::COLOR_RED,
11
+ 32 => Curses::COLOR_GREEN,
12
+ 33 => Curses::COLOR_YELLOW,
13
+ 34 => Curses::COLOR_BLUE,
14
+ 35 => Curses::COLOR_MAGENTA,
15
+ 36 => Curses::COLOR_CYAN,
16
+ 37 => Curses::COLOR_WHITE
17
+ }.freeze
18
+
19
+ # Regex to match ANSI color codes (like \e[31m)
20
+ ANSI_ESCAPE_REGEX = /\e\[(\d+(?:;\d+)*)m/
21
+
22
+ def parse_sgr_codes(codes)
23
+ color = nil
24
+ attr = Curses::A_NORMAL
25
+
26
+ codes.each do |code|
27
+ code = code.to_i
28
+ if ANSI_COLOR_MAP.key?(code)
29
+ color = ANSI_COLOR_MAP[code]
30
+ elsif code == 1
31
+ attr |= Curses::A_BOLD
32
+ elsif code == 4
33
+ attr |= Curses::A_UNDERLINE
34
+ elsif code.zero?
35
+ attr = Curses::A_NORMAL
36
+ color = nil
37
+ end
38
+ end
39
+
40
+ [color, attr]
41
+ end
42
+
43
+ def parse_ansi_and_render(line)
44
+ @win.clrtoeol
45
+ current_color = nil
46
+ current_attr = Curses::A_NORMAL
47
+
48
+ line.split(ANSI_ESCAPE_REGEX).each_with_index do |part, i|
49
+ if i.odd?
50
+ codes = part.split(";").map(&:to_i)
51
+ current_color, current_attr = parse_sgr_codes(codes)
52
+ elsif current_color
53
+ pair_id = current_color + 1
54
+ @win.attron(Curses.color_pair(pair_id) | current_attr) do
55
+ @win.addstr(part)
56
+ end
57
+ else
58
+ @win.attron(current_attr) do
59
+ @win.addstr(part)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ @device = ESPHome::Device.new(ARGV[0], ARGV[1], logger: Logger.new(IO::NULL))
66
+
67
+ def print_entity(entity, clear_line: true)
68
+ return unless @entities.key?(entity.key) # has an unsupported entity
69
+
70
+ @win.setpos(@entities[entity.key] + 3, 0)
71
+ @win.clrtoeol if clear_line
72
+ name = "#{entity.class.name.split("::").last} - #{entity.name}"
73
+ @win.addstr("#{name.ljust(40)}: [#{Time.now.strftime("%H:%M:%S")}] #{entity.formatted_state}")
74
+ end
75
+
76
+ def visible_lines
77
+ @win.maxy - @entities.size - 5
78
+ end
79
+
80
+ def render_log(visible_lines = nil)
81
+ visible_lines ||= self.visible_lines
82
+
83
+ @log_lines.each_with_index do |log_line, idx|
84
+ break if idx >= visible_lines
85
+
86
+ @win.setpos(@entities.size + 4 + idx, 0)
87
+
88
+ parse_ansi_and_render(log_line)
89
+ end
90
+ end
91
+
92
+ def render_all
93
+ @win.clear
94
+ @win.setpos(0, 0)
95
+ @win.addstr("ESPHome")
96
+ @win.setpos(1, 0)
97
+ @win.addstr("#{@device.friendly_name.ljust(45)} (#{@device.esphome_version} - #{@device.compilation_time})")
98
+ @win.setpos(2, 0)
99
+ @win.addstr("=" * 80)
100
+
101
+ @device.entities.each_value do |entity|
102
+ next unless entity.is_a?(ESPHome::Entity::HasState)
103
+
104
+ print_entity(entity, clear_line: false)
105
+ end
106
+
107
+ render_log
108
+
109
+ @win.refresh
110
+ end
111
+
112
+ Signal.trap("SIGWINCH") do
113
+ render_all
114
+ end
115
+
116
+ @log_lines = []
117
+
118
+ @device.on_message do |entity_or_log_line|
119
+ if entity_or_log_line.is_a?(String)
120
+ visible_lines = self.visible_lines
121
+ @log_lines << "[#{Time.now.strftime("%H:%M:%S")}]#{entity_or_log_line}"
122
+ @log_lines.shift if @log_lines.size > [20, visible_lines].max
123
+
124
+ render_log
125
+ else
126
+ print_entity(entity_or_log_line)
127
+ end
128
+ @win.refresh
129
+ end
130
+
131
+ @device.connect
132
+
133
+ @entities = {}
134
+ @device.entities.values.grep(ESPHome::Entity::HasState).sort_by(&:name).each_with_index do |entity, idx|
135
+ @entities[entity.key] = idx
136
+ end
137
+
138
+ Curses.init_screen
139
+ begin
140
+ Curses.noecho
141
+ Curses.start_color
142
+ Curses.use_default_colors
143
+ @win = Curses.stdscr
144
+ ANSI_COLOR_MAP.each_value do |color|
145
+ Curses.init_pair(color + 1, color, -1)
146
+ end
147
+
148
+ render_all
149
+
150
+ @device.stream_states
151
+ @device.stream_log(dump_config: true)
152
+ @device.loop
153
+ rescue Interrupt
154
+ # exitting
155
+ ensure
156
+ Curses.close_screen
157
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: esphome
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Cody Cutrer
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-07-01 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: curses
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.5'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.5'
26
+ - !ruby/object:Gem::Dependency
27
+ name: google-protobuf
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '4.31'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4.31'
40
+ - !ruby/object:Gem::Dependency
41
+ name: logger
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.7'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.7'
54
+ - !ruby/object:Gem::Dependency
55
+ name: noise-ruby
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.10'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.10'
68
+ email:
69
+ - cody@cutrer.us
70
+ executables:
71
+ - esphome-monitor
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - exe/esphome-monitor
76
+ homepage: https://github.com/ccutrer/esphome-ruby
77
+ licenses:
78
+ - MIT
79
+ metadata:
80
+ homepage_uri: https://github.com/ccutrer/esphome-ruby
81
+ rubygems_mfa_required: 'true'
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.1.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.6.3
97
+ specification_version: 4
98
+ summary: ESPHome Library and CLI for Ruby
99
+ test_files: []