Skip to content

Commit f0dbca2

Browse files
authored
Merge pull request #78 from NET-BYU/newBoard
It has happened. V2 hardware has been integrated with SSS.
2 parents 333f854 + 5a1b55c commit f0dbca2

File tree

5 files changed

+420
-6
lines changed

5 files changed

+420
-6
lines changed

display/physical_screen_v2.py

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import itertools
2+
import time
3+
4+
from .display import Display
5+
from .seven_seg_v2 import SevenSegment
6+
7+
8+
class PhysicalScreen:
9+
def __init__(self, brightness=3):
10+
self.brightness = brightness
11+
self.num_segs_across = 1
12+
self.num_segs_down = 1
13+
self._create_display()
14+
15+
def _create_display(self):
16+
# need to have an array of ip addresses if more panels
17+
panel_array = [
18+
[
19+
SevenSegment(ip_address="172.0.0.3", brightness=self.brightness)
20+
for j in range(self.num_segs_across)
21+
]
22+
for i in range(self.num_segs_down)
23+
]
24+
25+
self.display = Display(
26+
panel_array,
27+
self.num_segs_across * 16,
28+
self.num_segs_down * 6 * 2,
29+
)
30+
31+
def _close_display(self):
32+
for row in range(len(self.display.board_objects)):
33+
for panel in range(len(self.display.board_objects[row])):
34+
self.display.board_objects[row][panel].close()
35+
36+
def create_tick(self, frame_rate):
37+
period = 1.0 / frame_rate
38+
nextTime = time.time() + period
39+
40+
for i in itertools.count():
41+
now = time.time()
42+
toSleep = nextTime - now
43+
44+
if toSleep > 0:
45+
time.sleep(toSleep)
46+
nextTime += period
47+
else:
48+
nextTime = now + period
49+
50+
yield i, nextTime
51+
52+
def clear(self):
53+
self.display.clear()
54+
55+
def refresh(self):
56+
self._close_display()
57+
self._create_display()

display/seven_seg_v2.py

+327
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
import socket
2+
import struct
3+
4+
from display import symbols as sy # get_char2
5+
6+
MAX72XX_DIGITS = 8
7+
8+
MAX72XX_REG_NOOP = 0x0
9+
MAX72XX_REG_DIGIT0 = 0x1
10+
MAX72XX_REG_DIGIT1 = 0x02
11+
MAX72XX_REG_DIGIT2 = 0x3
12+
MAX72XX_REG_DIGIT3 = 0x4
13+
MAX72XX_REG_DIGIT4 = 0x5
14+
MAX72XX_REG_DIGIT5 = 0x6
15+
MAX72XX_REG_DIGIT6 = 0x7
16+
MAX72XX_REG_DIGIT7 = 0x8
17+
MAX72XX_REG_DECODEMODE = 0x9
18+
MAX72XX_REG_INTENSITY = 0xA
19+
MAX72XX_REG_SCANLIMIT = 0xB
20+
MAX72XX_REG_SHUTDOWN = 0xC
21+
MAX72XX_REG_DISPLAYTEST = 0xF
22+
23+
24+
DEFAULT_BAUDRATE = 2000000
25+
26+
27+
class SevenSegment:
28+
def __init__(
29+
self,
30+
baudrate=DEFAULT_BAUDRATE,
31+
ip_address="172.0.0.3",
32+
port=1883,
33+
brightness=7,
34+
clear=True,
35+
):
36+
"""Constructor
37+
38+
Args:
39+
baudrate (int): rate at which data is transfered (default 9000kHz), excessive rate may result in instability
40+
ip_address (string): ip address of the panel
41+
port (int): port of the panel
42+
brightness (int): starting brightness of the leds
43+
clear (bool): clear the screen on initialization
44+
"""
45+
self.num_digits = 96
46+
self.num_segments = 12
47+
self.num_per_segment = 8
48+
self.baudrate = baudrate if baudrate < 10000000 else 10000000
49+
# self._buf = [0] * self.num_digits
50+
self._command_buf = [0] * (2 + self.num_segments)
51+
self._buf = [0] * (2 + self.num_digits)
52+
self._buf[0] = 0 # data
53+
self._buf[1] = self.num_digits
54+
self._display_buf = [0] * self.num_digits
55+
self.addr = (ip_address, port)
56+
self.panel_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
57+
self.commandSerializer = struct.Struct("B" * (self.num_segments + 2))
58+
self.dataSerialer = struct.Struct("B" * (self.num_digits + 2))
59+
60+
# Setup the display
61+
self.command(MAX72XX_REG_SHUTDOWN, 1) # 1 enables the display
62+
self.command(
63+
MAX72XX_REG_DECODEMODE, 0
64+
) # 0x01, 0x0F, 0xFF for different Code B modes
65+
self.command(MAX72XX_REG_SCANLIMIT, self.num_per_segment - 1)
66+
self.command(MAX72XX_REG_DISPLAYTEST, 0)
67+
self.brightness(brightness)
68+
69+
# Set up cascaded segment orientation stuff to enable 2 functions
70+
self.display = [
71+
[1, 2],
72+
[3, 4],
73+
[5, 6],
74+
[7, 8],
75+
[9, 10],
76+
[11, 12],
77+
] # needed to make some functions work properly
78+
self._display_y_len = len(self.display) if self.display is not None else None
79+
80+
self._flush_index = []
81+
if clear:
82+
self.clear()
83+
84+
def command(self, register_num, value):
85+
"""
86+
Sets control registers for each segment in the display
87+
88+
Args:
89+
register_num (int): which register to set
90+
value (int or list(int)): value(s) to set the register to
91+
"""
92+
# check register_num is good
93+
if register_num not in [
94+
MAX72XX_REG_DECODEMODE,
95+
MAX72XX_REG_INTENSITY,
96+
MAX72XX_REG_SCANLIMIT,
97+
MAX72XX_REG_SHUTDOWN,
98+
MAX72XX_REG_DISPLAYTEST,
99+
]:
100+
raise ValueError(f"register_num is not a correct value: {register_num}")
101+
# check value is good
102+
if not isinstance(value, (int, list)):
103+
raise ValueError(f"value is not a correct type: {type(value)}")
104+
if type(value) == int and not (0 <= value < 16):
105+
raise ValueError(f"value is not within bounds [1:15]: {value}")
106+
if type(value) == list and not (max(value) < 16 and min(value) >= 0):
107+
raise ValueError(f"values in list are not within bounds [1:15]: {value}")
108+
# generate command buffer
109+
self._command_buf[0] = register_num
110+
self._command_buf[1] = self.num_segments
111+
self._command_buf[2:] = (
112+
value if type(value) == list else [value] * self.num_segments
113+
)
114+
self._write_command()
115+
116+
def close(self, clear=True, shutdown=True):
117+
"""
118+
Close the spi connection
119+
120+
Args:
121+
clear (bool): clear the display before closing
122+
shutdown (bool): shutdown the display before closing
123+
"""
124+
if clear:
125+
self.clear()
126+
if shutdown:
127+
self.command(MAX72XX_REG_SHUTDOWN, 0)
128+
# self._spi.close()
129+
self.panel_sock.close()
130+
131+
def clear(self, flush=True):
132+
"""
133+
Clears the buffer, and if specified, flushes the display
134+
135+
Args:
136+
flush (bool): flush the display after clearing
137+
"""
138+
self._buf[2:] = [0] * self.num_digits
139+
if flush:
140+
self.flush_legacy()
141+
142+
def brightness(self, value):
143+
"""
144+
Sets the brightness for all of the segments ranging from 0 - 15
145+
146+
Args:
147+
value (int) or (list): brightness value to set
148+
"""
149+
# check value is good
150+
if not isinstance(value, (int, list)):
151+
raise ValueError(f"value is not a correct type: {type(value)}")
152+
if type(value) == int and not (0 <= value < 16):
153+
raise ValueError(f"value is not within bounds [1:15]: {value}")
154+
if type(value) == list and not (max(value) < 16 and min(value) >= 0):
155+
raise ValueError(f"values in list are not within bounds [1:15]: {value}")
156+
self.command(MAX72XX_REG_INTENSITY, value)
157+
158+
# Original flush, about 2 times slower than the current flush function, used in clear
159+
def flush_legacy(self):
160+
"""Cascade the buffer onto the display"""
161+
self.flush()
162+
163+
def flush(self):
164+
"""Flush all the current changes to the display"""
165+
self._write_data()
166+
167+
def raw(self, position, value, flush=False):
168+
"""
169+
Given raw 0-255 value draw symbol at given postion
170+
171+
Args:
172+
position (int): position to draw the symbol
173+
value (int): value to draw at the position
174+
flush (bool): flush the display after drawing
175+
"""
176+
# Check if position is valid
177+
if (
178+
not isinstance(position, (int))
179+
or position < 0
180+
or position >= self.num_digits
181+
):
182+
raise ValueError("position is not a valid number")
183+
# Check if char is int between 0 and 255
184+
if not isinstance(value, (int)) or value < 0 or value > 255:
185+
raise ValueError("value is either not an int or out of bounds (0-255)")
186+
self._buf[position + 2] = value
187+
188+
if flush:
189+
self.flush()
190+
191+
def raw2(self, x, y, value, flush=False):
192+
"""
193+
Given raw 0-255 value draw symbol at given coordinate
194+
195+
Args:
196+
x (int): x coordinate to draw the symbol
197+
"""
198+
position = self._get_pos(x, y)
199+
self.raw(position, value, flush)
200+
201+
def letter(self, position, char, dot=False, flush=False):
202+
"""
203+
Outputs ascii letter as close as it can, working letters/symbols found in symbols.py
204+
205+
Args:
206+
position (int): position to draw the symbol
207+
char (str): character to draw at the position
208+
dot (bool): whether or not to draw a dot after the character
209+
flush (bool): flush the display after drawing
210+
"""
211+
# Check if position is valid
212+
if (
213+
not isinstance(position, (int))
214+
or position < 0
215+
or position >= self.num_digits
216+
):
217+
raise ValueError("position is not a valid number")
218+
value = sy.get_char2(char) | (dot << 7)
219+
self._buf[position + 2] = value
220+
if flush:
221+
self.flush()
222+
223+
def letter2(self, x, y, char, dot=False, flush=False):
224+
"""
225+
Output letter on the display at the coordinates provided if possible
226+
227+
Args:
228+
x (int): x coordinate to draw the symbol
229+
y (int): y coordinate to draw the symbol
230+
char (str): character to draw at the position
231+
dot (bool): whether or not to draw a dot after the character
232+
flush (bool): flush the display after drawing
233+
"""
234+
# Check to make sure segment array has been initialized
235+
if self.display is None:
236+
raise ValueError("segment_orientation_array has not been initialized")
237+
pos = self._get_pos(x, y)
238+
self.letter(pos, char, dot, flush)
239+
240+
def text(self, txt, start_position=0, flush=False):
241+
"""
242+
Output text on the display at the start position if possible
243+
244+
Args:
245+
txt (str): text to draw on the display
246+
start_position (int): position to start drawing the text
247+
flush (bool): flush the display after drawing
248+
"""
249+
# Check if txt is going to overflow buffer
250+
if start_position + len(txt.replace(".", "")) > self.num_digits:
251+
raise OverflowError("Message would overflow spi buffer")
252+
253+
for pos, char in enumerate(txt):
254+
# Check if current char is a dot and append to previous letter
255+
if char == "." and pos != 0: # mutliple dots in a row cause an error
256+
self.letter(pos + start_position - 1, txt[pos - 1], dot=True)
257+
else:
258+
self.letter(start_position + pos, char)
259+
260+
if flush:
261+
self.flush()
262+
263+
def text2(self, x, y, txt, horizontal=True, flush=False):
264+
"""
265+
Output text on the display at the given x, y - option to display horizontal or vertical text
266+
267+
Args:
268+
x (int): x coordinate to draw the symbol
269+
y (int): y coordinate to draw the symbol
270+
txt (str): text to draw on the display
271+
horizontal (bool): whether or not to draw the text horizontally
272+
flush (bool): flush the display after drawing
273+
"""
274+
# No initial checks and will let underlying functions do the work
275+
if horizontal:
276+
for pos, char in enumerate(txt):
277+
# Check if current char is a dot and append to previous letter
278+
if char == "." and pos != 0: # mutliple dots in a row cause an error
279+
self.letter2(x + pos - 1, y, txt[pos - 1], True)
280+
else:
281+
self.letter2(x + pos, y, char)
282+
else:
283+
for pos, char in enumerate(txt):
284+
# Check if current char is a dot and append to previous letter
285+
if char == "." and pos != 0: # mutliple dots in a row cause an error
286+
self.letter2(x, y + pos - 1, txt[pos - 1], True)
287+
else:
288+
self.letter2(x, y + pos, char)
289+
if flush:
290+
self.flush()
291+
292+
# Write data buffer to panel through socket
293+
def _write_data(self):
294+
self.panel_sock.sendto(self.dataSerialer.pack(*self._buf), self.addr)
295+
296+
# Write command buffer to panel through socket
297+
def _write_command(self):
298+
self.panel_sock.sendto(
299+
self.commandSerializer.pack(*self._command_buf), self.addr
300+
)
301+
302+
# Get position in the buffer for a given x,y coordinate
303+
def _get_pos(self, x, y):
304+
# Check y is within bounds
305+
if not isinstance(y, (int)) or y < 0 or y >= self._display_y_len:
306+
return ValueError("y value is not a valid number")
307+
308+
# Check if x is an int
309+
if not isinstance(x, (int)):
310+
return ValueError("x value is not an integer")
311+
x_seg = int(x / self.num_per_segment)
312+
313+
# check if x is within bounds of y row
314+
if x_seg >= len(self.display[y]):
315+
raise ValueError("x value is out of range")
316+
317+
return (self.display[y][x_seg] - 1) * self.num_per_segment + (
318+
x % self.num_per_segment
319+
)
320+
321+
# Not current in use
322+
def _check_buf(self):
323+
indices = []
324+
for pos in range(len(self._buf)):
325+
if self._buf[pos] != self._display_buf[pos]:
326+
indices.append(pos)
327+
return indices

0 commit comments

Comments
 (0)