barkest_lcd 0.4.0

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.
@@ -0,0 +1,91 @@
1
+ module BarkestLcd
2
+ ##
3
+ # A simple enumeration class based on a hash of integers.
4
+ #
5
+ # enum = HashEnum.new({ :alpha => 0x01, :bravo => 0x02, :charlie => 0x04 })
6
+ # enum.alpha # 1
7
+ # enum.bravo # 2
8
+ # enum.CHARLIE # 4
9
+ # enum.alpha?(11) # true
10
+ # enum.BRAVO?(11) # true
11
+ # enum.charlie?(11) # false
12
+ #
13
+ class HashEnum
14
+
15
+ ##
16
+ # Turns a hash into an enumeration.
17
+ def initialize(hash = {})
18
+ @hash = hash.inject({}){ |memo,(k,v)| memo[k.to_sym] = v.to_i; memo }.freeze
19
+ # values ordered highest to lowest.
20
+ @flags = @hash.to_a.sort{|a,b| b[1] <=> a[1]}
21
+
22
+ freeze
23
+ end
24
+
25
+ # :nodoc:
26
+ def method_missing(meth, *args, &block)
27
+
28
+ if @hash.respond_to?(meth)
29
+ return @hash.send(meth, *args, &block)
30
+ end
31
+
32
+ [ meth.to_s.downcase.to_sym, meth.to_s.upcase.to_sym ].each do |test_meth|
33
+ if @hash.keys.include?(test_meth)
34
+ return @hash[test_meth]
35
+ else
36
+ meth_name = test_meth.to_s
37
+ is_flag = meth_name[-1] == '?'
38
+ if is_flag
39
+ test_meth = meth_name[0..-2].to_sym
40
+ if @hash.keys.include?(test_meth)
41
+ valid = @hash[test_meth]
42
+ test = args && args.count > 0 ? args.first : nil
43
+ raise ArgumentError, 'Missing value to test.' unless test
44
+ raise ArgumentError, 'Test value must be an integer.' unless test.is_a?(Fixnum)
45
+ return (test & valid) == valid
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ super meth, *args, &block
52
+ end
53
+
54
+ ##
55
+ # Determines what keys the value contains.
56
+ #
57
+ # If a remainder exists, then the remainder is added at the end of the returned array.
58
+ def flags(value)
59
+ ret = []
60
+ @flags.each do |(k,v)|
61
+ if (value & v) == v
62
+ value -= v
63
+ ret << k
64
+ end
65
+ end
66
+ ret << value if value != 0
67
+ ret
68
+ end
69
+
70
+ # :nodoc:
71
+ def inspect
72
+ @hash.inspect
73
+ end
74
+
75
+ # :nodoc:
76
+ def to_s
77
+ @hash.to_s
78
+ end
79
+
80
+ # :nodoc:
81
+ def ==(other)
82
+ @hash == other
83
+ end
84
+
85
+ # :nodoc:
86
+ def eql?(other)
87
+ @hash.eql?(other)
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,184 @@
1
+
2
+ require 'models/error_logger'
3
+
4
+ module BarkestLcd
5
+ ##
6
+ # A class to interface with the picoLCD 256x64 (aka picoLCD Graphic) from [www.mini-box.com](http://www.mini-box.com).
7
+ #
8
+ #
9
+ class PicoLcdGraphic < ::HIDAPI::Device
10
+
11
+ include BarkestLcd::ErrorLogger
12
+
13
+ ##
14
+ # Any of the Pico LCD errors.
15
+ PicoLcdError = Class.new(StandardError)
16
+
17
+ ##
18
+ # The device has already been opened.
19
+ AlreadyOpen = Class.new(PicoLcdError)
20
+
21
+ ##
22
+ # The device is not currently open.
23
+ NotOpen = Class.new(PicoLcdError)
24
+
25
+ ##
26
+ # The device failed to open (no address returned).
27
+ OpenFailed = Class.new(PicoLcdError)
28
+
29
+ ##
30
+ # The operation has timed out.
31
+ Timeout = Class.new(PicoLcdError)
32
+
33
+ ##
34
+ # USB Vendor ID
35
+ VENDOR_ID = 0x04d8
36
+
37
+ ##
38
+ # USB Device ID
39
+ DEVICE_ID = 0xc002
40
+
41
+
42
+
43
+ ##
44
+ # Enumerates the picoLCD devices attached to the system.
45
+ def self.devices(refresh = false)
46
+ @devices = nil if refresh
47
+ @devices ||= HIDAPI.enumerate(VENDOR_ID, DEVICE_ID, as: 'BarkestLcd::PicoLcdGraphic')
48
+ end
49
+
50
+ ##
51
+ # Resets the device.
52
+ def reset
53
+ write [ OUT_REPORT.LCD_RESET, 0x01, 0x00 ]
54
+
55
+ (0...4).each do |csi|
56
+ cs = (csi << 2) & 0xFF
57
+ write [ OUT_REPORT.CMD, cs, 0x02, 0x00, 0x64, 0x3F, 0x00, 0x64, 0xC0 ]
58
+ end
59
+
60
+ reset_hook.each { |hook| hook.call(self) }
61
+
62
+ self
63
+ end
64
+
65
+
66
+ ##
67
+ # Processes any waiting input from the device and paints the screen if it is dirty.
68
+ def loop
69
+ data = read
70
+ if data && data.length > 0
71
+
72
+ type = data.getbyte(0)
73
+ data = data[1..-1]
74
+
75
+ input_hook(type).call(self, type, data)
76
+
77
+ end
78
+
79
+ loop_hook.each { |hook| hook.call(self) }
80
+
81
+ self
82
+ end
83
+
84
+
85
+
86
+ # :nodoc:
87
+ def self.method_missing(meth, *args, &block)
88
+ # pass through methods like 'first', 'count', 'each', etc to the 'devices' list.
89
+ list = devices
90
+ if list.respond_to?(meth)
91
+ list.send meth, *args, &block
92
+ else
93
+ super meth, *args, &block
94
+ end
95
+ end
96
+
97
+
98
+
99
+ protected
100
+
101
+
102
+ ##
103
+ # Hooks a block to run for a specific incoming type.
104
+ #
105
+ # Yields the device instance, type code, and the data to the block.
106
+ def input_hook(incoming_type, method_name = nil, &block)
107
+ @input_hook ||= {}
108
+
109
+ if block_given?
110
+ # set the hook.
111
+ @input_hook[incoming_type] = block
112
+ elsif method_name
113
+ @input_hook[incoming_type] = Proc.new { |dev, type, data| dev.send(method_name, dev, type, data) }
114
+ else
115
+ # get the hook
116
+ @input_hook[incoming_type] ||= Proc.new { |dev, type, data| HIDAPI.debug "no input hook for #{type} message type with #{data.length} bytes of data" }
117
+ end
118
+ end
119
+
120
+
121
+ ##
122
+ # Hooks a block to run during the loop method.
123
+ #
124
+ # Yields the device instance.
125
+ def loop_hook(method_name = nil, &block)
126
+ @loop_hook ||= []
127
+ if block_given?
128
+ @loop_hook << block
129
+ elsif method_name
130
+ @loop_hook << Proc.new { |dev| dev.send(method_name, dev) }
131
+ end
132
+ @loop_hook
133
+ end
134
+
135
+
136
+ ##
137
+ # Hooks a block to run during the reset method.
138
+ #
139
+ # Yields the device instance.
140
+ def reset_hook(method_name = nil, &block)
141
+ @reset_hook ||= []
142
+ if block_given?
143
+ @reset_hook << block
144
+ elsif method_name
145
+ @reset_hook << Proc.new { |dev| dev.send(method_name, dev) }
146
+ end
147
+ @reset_hook
148
+ end
149
+
150
+
151
+ ##
152
+ # Loops while the block returns true and the timeout hasn't expired.
153
+ def loop_while(options={}, &block)
154
+ timeout = options.delete(:timeout) || 2500
155
+ timeout = 10 if timeout < 10
156
+
157
+ result = block_given? ? block.call : true
158
+ while result
159
+ sleep 0.01
160
+ result = block_given? ? block.call : true
161
+ unless result
162
+ timeout -= 10
163
+ raise BarkestLcd::PicoLcdGraphic::Timeout if timeout < 0
164
+ end
165
+ end
166
+
167
+ true
168
+ end
169
+
170
+ # Turn off blocking for the PicoLcdGraphic.
171
+ # If the user decides, they can turn blocking back on.
172
+ init_hook do |dev|
173
+ dev.blocking = false
174
+ end
175
+
176
+
177
+
178
+ end
179
+ end
180
+
181
+ # Include the components of the model.
182
+ Dir.glob(File.expand_path('../pico_lcd_graphic/*.rb', __FILE__)).each do |file|
183
+ require file
184
+ end
@@ -0,0 +1,97 @@
1
+ require 'models/simple_graphic'
2
+
3
+ BarkestLcd::PicoLcdGraphic.class_eval do
4
+ include BarkestLcd::SimpleGraphic
5
+
6
+ init_hook :init_display
7
+
8
+
9
+ ##
10
+ # Width of the screen in pixels.
11
+ SCREEN_W = 256
12
+
13
+ ##
14
+ # Height of the screen in pixels.
15
+ SCREEN_H = 64
16
+
17
+ ##
18
+ # Default contrast for the screen.
19
+ DEFAULT_CONTRAST = 0xE5
20
+
21
+ ##
22
+ # Default brightness for the screen.
23
+ DEFAULT_BACKLIGHT = 0x7F
24
+
25
+
26
+ ##
27
+ # Sets the backlight level.
28
+ def backlight(level = 0xFF)
29
+ level &= 0xFF
30
+ write [ OUT_REPORT.LCD_BACKLIGHT, level ]
31
+ self
32
+ end
33
+
34
+
35
+ ##
36
+ # Sets the contrast level.
37
+ def contrast(level = 0xFF)
38
+ level &= 0xFF
39
+ write [ OUT_REPORT.LCD_CONTRAST, level ]
40
+ self
41
+ end
42
+
43
+
44
+ ##
45
+ # Sends the screen contents to the device.
46
+ def paint(force = false)
47
+ if dirty? || force
48
+ # 4 chips each holding 64x64 of data.
49
+ (0..3).each do |csi|
50
+ cs = (csi << 2)
51
+ # each memory line holds 64 bytes, or 8 rows of data.
52
+ (0..7).each do |line|
53
+ # use dirty rectangles to avoid sending unnecessary data to the device.
54
+ if force || dirty_rect?(csi * 64, line * 8, 64, 8)
55
+ # send the data in two packets for each memory line.
56
+ packet_1 = [ OUT_REPORT.CMD_DATA, cs, 0x02, 0x00, 0x00, 0xb8 | line, 0x00, 0x00, 0x40, 0x00, 0x00, 32 ]
57
+ packet_2 = [ OUT_REPORT.DATA, cs | 0x01, 0x00, 0x00, 32 ]
58
+
59
+ (0..63).each do |index|
60
+ # each byte holds the data for 8 rows.
61
+ byte = 0x00
62
+ (0..7).each do |bit|
63
+ x = (csi * 64) + index
64
+ y = ((line * 8) + bit) % height
65
+
66
+ byte |= (1 << bit) if get_bit(x, y)
67
+ end
68
+
69
+ # add the byte to the correct packet.
70
+ (index < 32 ? packet_1 : packet_2) << byte
71
+ end
72
+ # send the packets.
73
+ write packet_1
74
+ write packet_2
75
+ end
76
+ end
77
+ end
78
+ clear_dirty
79
+ end
80
+ self
81
+ end
82
+
83
+
84
+ private
85
+
86
+ def init_display(_)
87
+ init_graphic SCREEN_W, SCREEN_H
88
+ loop_hook { |_| paint }
89
+ reset_hook do |_|
90
+ clear.paint
91
+ contrast DEFAULT_CONTRAST
92
+ backlight DEFAULT_BACKLIGHT
93
+ end
94
+ end
95
+
96
+
97
+ end
@@ -0,0 +1,107 @@
1
+ require 'models/hash_enum'
2
+
3
+ BarkestLcd::PicoLcdGraphic.class_eval do
4
+
5
+ ##
6
+ # Status codes that may be returned from the device.
7
+ STATUS = BarkestLcd::HashEnum.new(
8
+ OK: 0x00,
9
+ ERASE: 0x01,
10
+ WRITE: 0x02,
11
+ READ: 0x03,
12
+ ERROR: 0xFF,
13
+ KEY: 0x10,
14
+ IR: 0x11,
15
+ VER: 0x12,
16
+ DISCONNECTED: 0x13,
17
+ )
18
+
19
+ ##
20
+ # Reports received from the device.
21
+ IN_REPORT = BarkestLcd::HashEnum.new(
22
+ POWER_STATE: 0x01,
23
+ KEY_STATE: 0x11,
24
+ IR_DATA: 0x21,
25
+ EXT_EE_DATA: 0x31,
26
+ INT_EE_DATA: 0x32,
27
+ )
28
+
29
+ ##
30
+ # Reports sent to the device.
31
+ OUT_REPORT = BarkestLcd::HashEnum.new(
32
+ LED_STATE: 0x81,
33
+ LCD_BACKLIGHT: 0x91,
34
+ LCD_CONTRAST: 0x92,
35
+ CMD: 0x94,
36
+ DATA: 0x95,
37
+ CMD_DATA: 0x96,
38
+ LCD_RESET: 0x93,
39
+ RELAY_ONOFF: 0xB1,
40
+ TESTSPLASH: 0xC1,
41
+ EXT_EE_READ: 0xA1,
42
+ EXT_EE_WRITE: 0xA2,
43
+ INT_EE_READ: 0xA3,
44
+ INT_EE_WRITE: 0xA4,
45
+ )
46
+
47
+ ##
48
+ # Splash IDs.
49
+ ID_SPLASH = BarkestLcd::HashEnum.new(
50
+ TIMER: 0x72,
51
+ CYCLE_START: 0x72,
52
+ CYCLE_END: 0x73,
53
+ )
54
+
55
+
56
+ ##
57
+ # Types for flash operations.
58
+ FLASH_TYPE = BarkestLcd::HashEnum.new(
59
+ CODE_MEMORY: 0x00,
60
+ EPROM_EXTERNAL: 0x01,
61
+ EPROM_INTERNAL: 0x02,
62
+ CODE_SPLASH: 0x03,
63
+ )
64
+
65
+ ##
66
+ # HID reports for device.
67
+ HID_REPORT = BarkestLcd::HashEnum.new(
68
+ GET_VERSION_1: 0xF1,
69
+ GET_VERSION_2: 0xF7,
70
+ GET_MAX_STX_SIZE: 0xF6,
71
+ EXIT_FLASHER: 0xFF,
72
+ EXIT_KEYBOARD: 0xEF,
73
+ SET_SNOOZE_TIME: 0xF8,
74
+ ERROR: 0x10,
75
+ )
76
+
77
+ ##
78
+ # Flash reports for the device.
79
+ FLASH_REPORT = BarkestLcd::HashEnum.new(
80
+ ERASE_MEMORY: 0xF2,
81
+ READ_MEMORY: 0xF3,
82
+ WRITE_MEMORY: 0xF4,
83
+ )
84
+
85
+ ##
86
+ # Keyboard reports for the device.
87
+ KEYBD_REPORT = BarkestLcd::HashEnum.new(
88
+ ERASE_MEMORY: 0xB2,
89
+ READ_MEMORY: 0xB3,
90
+ WRITE_MEMORY: 0xB4,
91
+ MEMORY: 0x41,
92
+ )
93
+
94
+ ##
95
+ # Request results.
96
+ RESULT = BarkestLcd::HashEnum.new(
97
+ OK: 0x00,
98
+ PARAM_MISSING: 0x01,
99
+ DATA_MISSING: 0x02,
100
+ BLOCK_READ_ONLY: 0x03,
101
+ BLOCK_NOT_ERASABLE: 0x04,
102
+ BLOCK_TOO_BIG: 0x05,
103
+ SECTION_OVERFLOW: 0x06,
104
+
105
+ )
106
+
107
+ end