-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #78 from NET-BYU/newBoard
It has happened. V2 hardware has been integrated with SSS.
- Loading branch information
Showing
5 changed files
with
420 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import itertools | ||
import time | ||
|
||
from .display import Display | ||
from .seven_seg_v2 import SevenSegment | ||
|
||
|
||
class PhysicalScreen: | ||
def __init__(self, brightness=3): | ||
self.brightness = brightness | ||
self.num_segs_across = 1 | ||
self.num_segs_down = 1 | ||
self._create_display() | ||
|
||
def _create_display(self): | ||
# need to have an array of ip addresses if more panels | ||
panel_array = [ | ||
[ | ||
SevenSegment(ip_address="172.0.0.3", brightness=self.brightness) | ||
for j in range(self.num_segs_across) | ||
] | ||
for i in range(self.num_segs_down) | ||
] | ||
|
||
self.display = Display( | ||
panel_array, | ||
self.num_segs_across * 16, | ||
self.num_segs_down * 6 * 2, | ||
) | ||
|
||
def _close_display(self): | ||
for row in range(len(self.display.board_objects)): | ||
for panel in range(len(self.display.board_objects[row])): | ||
self.display.board_objects[row][panel].close() | ||
|
||
def create_tick(self, frame_rate): | ||
period = 1.0 / frame_rate | ||
nextTime = time.time() + period | ||
|
||
for i in itertools.count(): | ||
now = time.time() | ||
toSleep = nextTime - now | ||
|
||
if toSleep > 0: | ||
time.sleep(toSleep) | ||
nextTime += period | ||
else: | ||
nextTime = now + period | ||
|
||
yield i, nextTime | ||
|
||
def clear(self): | ||
self.display.clear() | ||
|
||
def refresh(self): | ||
self._close_display() | ||
self._create_display() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,327 @@ | ||
import socket | ||
import struct | ||
|
||
from display import symbols as sy # get_char2 | ||
|
||
MAX72XX_DIGITS = 8 | ||
|
||
MAX72XX_REG_NOOP = 0x0 | ||
MAX72XX_REG_DIGIT0 = 0x1 | ||
MAX72XX_REG_DIGIT1 = 0x02 | ||
MAX72XX_REG_DIGIT2 = 0x3 | ||
MAX72XX_REG_DIGIT3 = 0x4 | ||
MAX72XX_REG_DIGIT4 = 0x5 | ||
MAX72XX_REG_DIGIT5 = 0x6 | ||
MAX72XX_REG_DIGIT6 = 0x7 | ||
MAX72XX_REG_DIGIT7 = 0x8 | ||
MAX72XX_REG_DECODEMODE = 0x9 | ||
MAX72XX_REG_INTENSITY = 0xA | ||
MAX72XX_REG_SCANLIMIT = 0xB | ||
MAX72XX_REG_SHUTDOWN = 0xC | ||
MAX72XX_REG_DISPLAYTEST = 0xF | ||
|
||
|
||
DEFAULT_BAUDRATE = 2000000 | ||
|
||
|
||
class SevenSegment: | ||
def __init__( | ||
self, | ||
baudrate=DEFAULT_BAUDRATE, | ||
ip_address="172.0.0.3", | ||
port=1883, | ||
brightness=7, | ||
clear=True, | ||
): | ||
"""Constructor | ||
Args: | ||
baudrate (int): rate at which data is transfered (default 9000kHz), excessive rate may result in instability | ||
ip_address (string): ip address of the panel | ||
port (int): port of the panel | ||
brightness (int): starting brightness of the leds | ||
clear (bool): clear the screen on initialization | ||
""" | ||
self.num_digits = 96 | ||
self.num_segments = 12 | ||
self.num_per_segment = 8 | ||
self.baudrate = baudrate if baudrate < 10000000 else 10000000 | ||
# self._buf = [0] * self.num_digits | ||
self._command_buf = [0] * (2 + self.num_segments) | ||
self._buf = [0] * (2 + self.num_digits) | ||
self._buf[0] = 0 # data | ||
self._buf[1] = self.num_digits | ||
self._display_buf = [0] * self.num_digits | ||
self.addr = (ip_address, port) | ||
self.panel_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
self.commandSerializer = struct.Struct("B" * (self.num_segments + 2)) | ||
self.dataSerialer = struct.Struct("B" * (self.num_digits + 2)) | ||
|
||
# Setup the display | ||
self.command(MAX72XX_REG_SHUTDOWN, 1) # 1 enables the display | ||
self.command( | ||
MAX72XX_REG_DECODEMODE, 0 | ||
) # 0x01, 0x0F, 0xFF for different Code B modes | ||
self.command(MAX72XX_REG_SCANLIMIT, self.num_per_segment - 1) | ||
self.command(MAX72XX_REG_DISPLAYTEST, 0) | ||
self.brightness(brightness) | ||
|
||
# Set up cascaded segment orientation stuff to enable 2 functions | ||
self.display = [ | ||
[1, 2], | ||
[3, 4], | ||
[5, 6], | ||
[7, 8], | ||
[9, 10], | ||
[11, 12], | ||
] # needed to make some functions work properly | ||
self._display_y_len = len(self.display) if self.display is not None else None | ||
|
||
self._flush_index = [] | ||
if clear: | ||
self.clear() | ||
|
||
def command(self, register_num, value): | ||
""" | ||
Sets control registers for each segment in the display | ||
Args: | ||
register_num (int): which register to set | ||
value (int or list(int)): value(s) to set the register to | ||
""" | ||
# check register_num is good | ||
if register_num not in [ | ||
MAX72XX_REG_DECODEMODE, | ||
MAX72XX_REG_INTENSITY, | ||
MAX72XX_REG_SCANLIMIT, | ||
MAX72XX_REG_SHUTDOWN, | ||
MAX72XX_REG_DISPLAYTEST, | ||
]: | ||
raise ValueError(f"register_num is not a correct value: {register_num}") | ||
# check value is good | ||
if not isinstance(value, (int, list)): | ||
raise ValueError(f"value is not a correct type: {type(value)}") | ||
if type(value) == int and not (0 <= value < 16): | ||
raise ValueError(f"value is not within bounds [1:15]: {value}") | ||
if type(value) == list and not (max(value) < 16 and min(value) >= 0): | ||
raise ValueError(f"values in list are not within bounds [1:15]: {value}") | ||
# generate command buffer | ||
self._command_buf[0] = register_num | ||
self._command_buf[1] = self.num_segments | ||
self._command_buf[2:] = ( | ||
value if type(value) == list else [value] * self.num_segments | ||
) | ||
self._write_command() | ||
|
||
def close(self, clear=True, shutdown=True): | ||
""" | ||
Close the spi connection | ||
Args: | ||
clear (bool): clear the display before closing | ||
shutdown (bool): shutdown the display before closing | ||
""" | ||
if clear: | ||
self.clear() | ||
if shutdown: | ||
self.command(MAX72XX_REG_SHUTDOWN, 0) | ||
# self._spi.close() | ||
self.panel_sock.close() | ||
|
||
def clear(self, flush=True): | ||
""" | ||
Clears the buffer, and if specified, flushes the display | ||
Args: | ||
flush (bool): flush the display after clearing | ||
""" | ||
self._buf[2:] = [0] * self.num_digits | ||
if flush: | ||
self.flush_legacy() | ||
|
||
def brightness(self, value): | ||
""" | ||
Sets the brightness for all of the segments ranging from 0 - 15 | ||
Args: | ||
value (int) or (list): brightness value to set | ||
""" | ||
# check value is good | ||
if not isinstance(value, (int, list)): | ||
raise ValueError(f"value is not a correct type: {type(value)}") | ||
if type(value) == int and not (0 <= value < 16): | ||
raise ValueError(f"value is not within bounds [1:15]: {value}") | ||
if type(value) == list and not (max(value) < 16 and min(value) >= 0): | ||
raise ValueError(f"values in list are not within bounds [1:15]: {value}") | ||
self.command(MAX72XX_REG_INTENSITY, value) | ||
|
||
# Original flush, about 2 times slower than the current flush function, used in clear | ||
def flush_legacy(self): | ||
"""Cascade the buffer onto the display""" | ||
self.flush() | ||
|
||
def flush(self): | ||
"""Flush all the current changes to the display""" | ||
self._write_data() | ||
|
||
def raw(self, position, value, flush=False): | ||
""" | ||
Given raw 0-255 value draw symbol at given postion | ||
Args: | ||
position (int): position to draw the symbol | ||
value (int): value to draw at the position | ||
flush (bool): flush the display after drawing | ||
""" | ||
# Check if position is valid | ||
if ( | ||
not isinstance(position, (int)) | ||
or position < 0 | ||
or position >= self.num_digits | ||
): | ||
raise ValueError("position is not a valid number") | ||
# Check if char is int between 0 and 255 | ||
if not isinstance(value, (int)) or value < 0 or value > 255: | ||
raise ValueError("value is either not an int or out of bounds (0-255)") | ||
self._buf[position + 2] = value | ||
|
||
if flush: | ||
self.flush() | ||
|
||
def raw2(self, x, y, value, flush=False): | ||
""" | ||
Given raw 0-255 value draw symbol at given coordinate | ||
Args: | ||
x (int): x coordinate to draw the symbol | ||
""" | ||
position = self._get_pos(x, y) | ||
self.raw(position, value, flush) | ||
|
||
def letter(self, position, char, dot=False, flush=False): | ||
""" | ||
Outputs ascii letter as close as it can, working letters/symbols found in symbols.py | ||
Args: | ||
position (int): position to draw the symbol | ||
char (str): character to draw at the position | ||
dot (bool): whether or not to draw a dot after the character | ||
flush (bool): flush the display after drawing | ||
""" | ||
# Check if position is valid | ||
if ( | ||
not isinstance(position, (int)) | ||
or position < 0 | ||
or position >= self.num_digits | ||
): | ||
raise ValueError("position is not a valid number") | ||
value = sy.get_char2(char) | (dot << 7) | ||
self._buf[position + 2] = value | ||
if flush: | ||
self.flush() | ||
|
||
def letter2(self, x, y, char, dot=False, flush=False): | ||
""" | ||
Output letter on the display at the coordinates provided if possible | ||
Args: | ||
x (int): x coordinate to draw the symbol | ||
y (int): y coordinate to draw the symbol | ||
char (str): character to draw at the position | ||
dot (bool): whether or not to draw a dot after the character | ||
flush (bool): flush the display after drawing | ||
""" | ||
# Check to make sure segment array has been initialized | ||
if self.display is None: | ||
raise ValueError("segment_orientation_array has not been initialized") | ||
pos = self._get_pos(x, y) | ||
self.letter(pos, char, dot, flush) | ||
|
||
def text(self, txt, start_position=0, flush=False): | ||
""" | ||
Output text on the display at the start position if possible | ||
Args: | ||
txt (str): text to draw on the display | ||
start_position (int): position to start drawing the text | ||
flush (bool): flush the display after drawing | ||
""" | ||
# Check if txt is going to overflow buffer | ||
if start_position + len(txt.replace(".", "")) > self.num_digits: | ||
raise OverflowError("Message would overflow spi buffer") | ||
|
||
for pos, char in enumerate(txt): | ||
# Check if current char is a dot and append to previous letter | ||
if char == "." and pos != 0: # mutliple dots in a row cause an error | ||
self.letter(pos + start_position - 1, txt[pos - 1], dot=True) | ||
else: | ||
self.letter(start_position + pos, char) | ||
|
||
if flush: | ||
self.flush() | ||
|
||
def text2(self, x, y, txt, horizontal=True, flush=False): | ||
""" | ||
Output text on the display at the given x, y - option to display horizontal or vertical text | ||
Args: | ||
x (int): x coordinate to draw the symbol | ||
y (int): y coordinate to draw the symbol | ||
txt (str): text to draw on the display | ||
horizontal (bool): whether or not to draw the text horizontally | ||
flush (bool): flush the display after drawing | ||
""" | ||
# No initial checks and will let underlying functions do the work | ||
if horizontal: | ||
for pos, char in enumerate(txt): | ||
# Check if current char is a dot and append to previous letter | ||
if char == "." and pos != 0: # mutliple dots in a row cause an error | ||
self.letter2(x + pos - 1, y, txt[pos - 1], True) | ||
else: | ||
self.letter2(x + pos, y, char) | ||
else: | ||
for pos, char in enumerate(txt): | ||
# Check if current char is a dot and append to previous letter | ||
if char == "." and pos != 0: # mutliple dots in a row cause an error | ||
self.letter2(x, y + pos - 1, txt[pos - 1], True) | ||
else: | ||
self.letter2(x, y + pos, char) | ||
if flush: | ||
self.flush() | ||
|
||
# Write data buffer to panel through socket | ||
def _write_data(self): | ||
self.panel_sock.sendto(self.dataSerialer.pack(*self._buf), self.addr) | ||
|
||
# Write command buffer to panel through socket | ||
def _write_command(self): | ||
self.panel_sock.sendto( | ||
self.commandSerializer.pack(*self._command_buf), self.addr | ||
) | ||
|
||
# Get position in the buffer for a given x,y coordinate | ||
def _get_pos(self, x, y): | ||
# Check y is within bounds | ||
if not isinstance(y, (int)) or y < 0 or y >= self._display_y_len: | ||
return ValueError("y value is not a valid number") | ||
|
||
# Check if x is an int | ||
if not isinstance(x, (int)): | ||
return ValueError("x value is not an integer") | ||
x_seg = int(x / self.num_per_segment) | ||
|
||
# check if x is within bounds of y row | ||
if x_seg >= len(self.display[y]): | ||
raise ValueError("x value is out of range") | ||
|
||
return (self.display[y][x_seg] - 1) * self.num_per_segment + ( | ||
x % self.num_per_segment | ||
) | ||
|
||
# Not current in use | ||
def _check_buf(self): | ||
indices = [] | ||
for pos in range(len(self._buf)): | ||
if self._buf[pos] != self._display_buf[pos]: | ||
indices.append(pos) | ||
return indices |
Oops, something went wrong.