|
| 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