@@ -70,6 +70,13 @@ def __init__(self):
7070 super ().__init__ ("Reply: Invalid payload." )
7171
7272
73+ class InvalidValueError (InterfaceError ):
74+ """Exception indicating that a `REPLY_INVALID_VALUE` reply was received."""
75+ def __init__ (self ):
76+ """Initializes a new instance."""
77+ super ().__init__ ('Reply: Invalid value.' )
78+
79+
7380class Interface :
7481 """
7582 Class encapsulating the serial interface provided by the SuperKey hardware.
@@ -103,79 +110,177 @@ def autokey(self, string: str):
103110 """
104111 Sends the `REQUEST_AUTOKEY` command. Queues the specified string to be automatically keyed.
105112 """
106- # Assemble packet
107- payload = bytes (string , encoding = 'ascii' ) + b'\x00 ' # null char is not added by default
108- size = len (payload )
109- crc = self .__class__ .__crc16 (payload )
110- header = self .__class__ .__pack_header (MessageID .REQUEST_AUTOKEY , size , crc )
113+ self .__send_packet (MessageID .REQUEST_AUTOKEY , bytes (string , encoding = 'ascii' ) + b'\x00 ' )
114+ self .__check_reply_empty ()
111115
112- # Send packet
113- self .__send (header )
114- self .__send (payload )
115- self .__check_empty_reply ()
116+ def get_buzzer_enabled (self ) -> bool :
117+ """
118+ Sends the `REQUEST_GET_BUZZER_ENABLED` command. Returns whether the buzzer is enabled or not.
119+ """
120+ self .__send_packet (MessageID .REQUEST_GET_BUZZER_ENABLED )
121+ return self .__check_reply ('<?' )[0 ]
122+
123+ def get_buzzer_frequency (self ) -> int :
124+ """
125+ Sends the `REQUEST_GET_BUZZER_FREQUENCY` command. Returns the current buzzer frequency, in Hz.
126+ """
127+ self .__send_packet (MessageID .REQUEST_GET_BUZZER_FREQUENCY )
128+ return self .__check_reply ('<H' )[0 ]
129+
130+ def get_invert_paddles (self ) -> bool :
131+ """
132+ Sends the `REQUEST_GET_INVERT_PADDLES` command. Returns whether or not the paddles are inverted.
133+ """
134+ self .__send_packet (MessageID .REQUEST_GET_INVERT_PADDLES )
135+ return self .__check_reply ('<?' )[0 ]
136+
137+ def get_io_polarity (self , pin : IOPin ) -> IOPolarity :
138+ """
139+ Sends the `REQUEST_GET_IO_POLARITY` command. Returns the polarity of the specified I/O pin.
140+ """
141+ self .__send_packet (MessageID .REQUEST_GET_IO_POLARITY , struct .pack ('<B' , pin ))
142+ return IOPolarity (self .__check_reply ('<B' )[0 ])
143+
144+ def get_io_state (self , pin : IOPin ) -> IOPin :
145+ """
146+ Sends the `REQUEST_GET_IO_STATE` command. Returns `true` if the specified input / output pin is active.
147+ """
148+ self .__send_packet (MessageID .REQUEST_GET_IO_STATE , struct .pack ('<B' , pin ))
149+ return self .__check_reply ('<?' )[0 ]
150+
151+ def get_io_state_for_type (self , type : IOType ) -> IOType :
152+ """
153+ Sends the `REQUEST_GET_IO_STATE_FOR_TYPE` command. Returns `true` if any I/O pin with the specified type is on.
154+ """
155+ self .__send_packet (MessageID .REQUEST_GET_IO_STATE_FOR_TYPE , struct .pack ('<B' , type ))
156+ return self .__check_reply ('<?' )[0 ]
157+
158+ def get_io_type (self , pin : IOPin ) -> IOType :
159+ """
160+ Sends the `REQUEST_GET_IO_TYPE` command. Returns the type of the specified I/O pin.
161+ """
162+ self .__send_packet (MessageID .REQUEST_GET_IO_TYPE , struct .pack ('<B' , pin ))
163+ return IOType (self .__check_reply ('<B' )[0 ])
164+
165+ def get_led_enabled (self , led : LED ) -> bool :
166+ """
167+ Sends the `REQUEST_GET_LED_ENABLED` command. Returns whether or not the specified LED is enabled.
168+ """
169+ self .__send_packet (MessageID .REQUEST_GET_LED_ENABLED , struct .pack ('<b' , led ))
170+ return self .__check_reply ('<?' )[0 ]
171+
172+ def get_paddle_mode (self ) -> PaddleMode :
173+ """
174+ Sends the `REQUEST_GET_PADDLE_MODE` command. Returns the currently selected paddle mode.
175+ """
176+ self .__send_packet (MessageID .REQUEST_GET_PADDLE_MODE )
177+ return PaddleMode (self .__check_reply ('<B' )[0 ])
178+
179+ def get_wpm (self ) -> float :
180+ """
181+ Sends the `REQUEST_GET_WPM` command. Returns the current WPM setting.
182+ """
183+ self .__send_packet (MessageID .REQUEST_GET_WPM )
184+ return self .__check_reply ('<f' )[0 ]
185+
186+ def get_wpm_scale (self , element : CodeElement ) -> float :
187+ """
188+ Sends the `REQUEST_GET_WPM_SCALE` command. Returns the current WPM scale for the specified code element.
189+ """
190+ self .__send_packet (MessageID .REQUEST_GET_WPM_SCALE , struct .pack ('<B' , element ))
191+ return self .__check_reply ('<f' )[0 ]
116192
117193 def panic (self ):
118194 """
119195 Sends the `REQUEST_PANIC` command. Immediately and unconditionally stops keying.
120196 """
121- # Assemble packet
122- header = self .__class__ .__pack_header (MessageID .REQUEST_PANIC )
123-
124- # Send packet
125- self .__send (header )
126- self .__check_empty_reply ()
197+ self .__send_packet (MessageID .REQUEST_PANIC )
198+ self .__check_reply_empty ()
127199
128200 def ping (self ):
129201 """
130202 Sends the `REQUEST_PING` command. Keys a short test message.
131203 """
132- # Assemble packet
133- header = self .__class__ .__pack_header (MessageID .REQUEST_PING )
134-
135- # Send packet
136- self .__send (header )
137- self .__check_empty_reply ()
204+ self .__send_packet (MessageID .REQUEST_PING )
205+ self .__check_reply_empty ()
138206
139207 def restore_default_config (self ):
140208 """
141209 Sends the `REQUEST_RESTORE_DEFAULT_CONFIG` command. Restores the device to its default configuration.
142210 """
143- # Assemble packet
144- header = self .__class__ .__pack_header (MessageID .REQUEST_RESTORE_DEFAULT_CONFIG )
145-
146- # Send packet
147- self .__send (header )
148- self .__check_empty_reply ()
211+ self .__send_packet (MessageID .REQUEST_RESTORE_DEFAULT_CONFIG )
212+ self .__check_reply_empty ()
149213
150214 def set_buzzer_enabled (self , enabled : bool ):
151215 """
152216 Sends the `REQUEST_SET_BUZZER_ENABLED` command. Enables or disables the device's built-in buzzer.
153217 """
154- # Assemble packet
155- payload = struct .pack ('<?' , enabled )
156- size = len (payload )
157- crc = self .__class__ .__crc16 (payload )
158- header = self .__class__ .__pack_header (MessageID .REQUEST_SET_BUZZER_ENABLED , size , crc )
159-
160- # Send packet
161- self .__send (header )
162- self .__send (payload )
163- self .__check_empty_reply ()
218+ self .__send_packet (MessageID .REQUEST_SET_BUZZER_ENABLED , struct .pack ('<?' , enabled ))
219+ self .__check_reply_empty ()
164220
165221 def set_buzzer_frequency (self , frequency : int ):
166222 """
167223 Sends the `REQUEST_SET_BUZZER_FREQUENCY` command. Sets the frequency (in Hz) of the device's built-in buzzer.
168224 """
169- # Assemble packet
170- payload = struct .pack ('<H' , frequency )
171- size = len (payload )
172- crc = self .__class__ .__crc16 (payload )
173- header = self .__class__ .__pack_header (MessageID .REQUEST_SET_BUZZER_FREQUENCY , size , crc )
225+ self .__send_packet (MessageID .REQUEST_SET_BUZZER_FREQUENCY , struct .pack ('<H' , frequency ))
226+ self .__check_reply_empty ()
227+
228+ def set_invert_paddles (self , inverted : bool ):
229+ """
230+ Sends the `REQUEST_SET_INVERT_PADDLES` command. Sets whether the paddles are inverted.
231+ """
232+ self .__send_packet (MessageID .REQUEST_SET_INVERT_PADDLES , struct .pack ('<?' , inverted ))
233+ self .__check_reply_empty ()
234+
235+ def set_io_polarity (self , pin : IOPin , polarity : IOPolarity ):
236+ """
237+ Sends the `REQUEST_SET_IO_POLARITY` command. Sets the polarity of the specified I/O pin.
238+ """
239+ self .__send_packet (MessageID .REQUEST_SET_IO_POLARITY , struct .pack ('<BB' , pin , polarity ))
240+ self .__check_reply_empty ()
241+
242+ def set_io_type (self , pin : IOPin , type : IOType ):
243+ """
244+ Sends the `REQUEST_SET_IO_TYPE` command. Sets the type of the specified I/O pin.
245+ """
246+ self .__send_packet (MessageID .REQUEST_SET_IO_TYPE , struct .pack ('<BB' , pin , type ))
247+ self .__check_reply_empty ()
248+
249+ def set_led_enabled (self , led : LED , enabled : bool ):
250+ """
251+ Sends the `REQUEST_SET_LED_ENABLED` command. Sets whether the specified LED is enabled or not.
252+ """
253+ self .__send_packet (MessageID .REQUEST_SET_LED_ENABLED , struct .pack ('<B?' , led , enabled ))
254+ self .__check_reply_empty ()
255+
256+ def set_paddle_mode (self , mode : PaddleMode ):
257+ """
258+ Sends the `REQUEST_SET_PADDLE_MODE` command. Sets the current keyer paddle mode.
259+ """
260+ self .__send_packet (MessageID .REQUEST_SET_PADDLE_MODE , struct .pack ('<B' , mode ))
261+ self .__check_reply_empty ()
262+
263+ def set_wpm (self , wpm : float ):
264+ """
265+ Sends the `REQUEST_SET_WPM` command. Sets the keyer's WPM setting.
266+ """
267+ self .__send_packet (MessageID .REQUEST_SET_WPM , struct .pack ('<f' , wpm ))
268+ self .__check_reply_empty ()
269+
270+ def set_wpm_scale (self , element : CodeElement , scale : float ):
271+ """
272+ Sends the `REQUEST_SET_WPM_SCALE` command. Sets the WPM scale for the specified code element.
273+ """
274+ self .__send_packet (MessageID .REQUEST_SET_WPM_SCALE , struct .pack ('<Bf' , element , scale ))
275+ self .__check_reply_empty ()
174276
175- # Send packet
277+ def version (self ) -> str :
278+ """
279+ Sends the `REQUEST_VERSION` command. Returns the device's version information.
280+ """
281+ header = self .__class__ .__pack_header (MessageID .REQUEST_VERSION )
176282 self .__send (header )
177- self .__send (payload )
178- self .__check_empty_reply ()
283+ return self .__check_reply_str ()
179284
180285 def __validate_serial (self ):
181286 """
@@ -184,21 +289,117 @@ def __validate_serial(self):
184289 if self .serial is None :
185290 raise InterfaceError ("The serial port is not open." )
186291
187- def __check_empty_reply (self ) -> bool :
292+ def __send (self , buffer : bytes ) :
188293 """
189- Attempts to receive a generic empty reply from the device.
294+ Transmits the specified buffer.
295+ """
296+ self .__validate_serial ()
297+ self .serial .write (buffer )
298+
299+ def __send_packet (self , message : MessageID , payload : Optional [bytes ] = None ):
300+ """
301+ Sends a packet with the specified message ID and payload.
302+ """
303+ # Get header
304+ size = 0
305+ crc = 0
306+ if payload is not None :
307+ size = len (payload )
308+ crc = self .__class__ .__crc16 (payload )
309+ header = self .__class__ .__pack_header (message , size , crc )
310+
311+ # Send data
312+ self .__send (header )
313+ if payload is not None :
314+ self .__send (payload )
315+
316+ def __receive (self , size : int ):
317+ """
318+ Receives the specified number of bytes.
319+ """
320+ self .__validate_serial ()
321+ return self .serial .read (size = size )
322+
323+ def __receive_header (self ):
324+ """
325+ Attempts to receive a header from the serial port.
190326 """
191327 # Receive reply and verify we got enough data
192328 reply = self .__receive (HEADER_STRUCT_SIZE )
193329 if len (reply ) != HEADER_STRUCT_SIZE :
194330 raise InterfaceError ('No reply received.' )
195331
196332 # Unpack the header and verify the size and CRC are correct
197- message , size , crc = self .__class__ .__unpack_header (reply )
333+ return self .__class__ .__unpack_header (reply )
334+
335+ def __check_reply (self , format : str ) -> Tuple [any , ...]:
336+ """
337+ Attempts to receive a reply with a payload from the device.
338+ """
339+ # Unpack header
340+ message , size , crc = self .__receive_header ()
341+
342+ # Receive payload
343+ if size != 0 :
344+ payload = self .__receive (size )
345+ if len (payload ) != size :
346+ raise InterfaceError ('No payload received.' )
347+ else :
348+ payload = None
349+
350+ # Check the message ID
351+ self .__check_reply_message_id (message )
352+
353+ # Check CRC
354+ if payload is not None and self .__class__ .__crc16 (payload ) != crc :
355+ raise InterfaceError ('Reply: Invalid CRC?' )
356+
357+ # Check payload length
358+ if len (payload ) != struct .calcsize (format ):
359+ raise InterfaceError ('Reply: Invalid payload?' )
360+
361+ return struct .unpack (format , payload )
362+
363+ def __check_reply_empty (self ):
364+ """
365+ Attempts to receive a generic empty reply from the device.
366+ """
367+ # Unpack message
368+ message , size , crc = self .__receive_header ()
198369 if size != 0 or crc != 0 :
199370 raise InterfaceError ('Reply: Invalid size / CRC?' )
200371
201372 # Check the message ID
373+ self .__check_reply_message_id (message )
374+
375+ def __check_reply_str (self ) -> Optional [str ]:
376+ """
377+ Attempts to receive a reply with a string payload from the device.
378+ """
379+ # Unpack header
380+ message , size , crc = self .__receive_header ()
381+
382+ # Receive payload
383+ if size != 0 :
384+ payload = self .__receive (size )
385+ if len (payload ) != size :
386+ raise InterfaceError ('No payload received.' )
387+ else :
388+ return None
389+
390+ # Check the message ID
391+ self .__check_reply_message_id (message )
392+
393+ # Check CRC
394+ if self .__class__ .__crc16 (payload ) != crc :
395+ raise InterfaceError ('Reply: Invalid CRC?' )
396+
397+ return str (payload , encoding = 'ascii' )
398+
399+ def __check_reply_message_id (self , message : MessageID ):
400+ """
401+ Throws an exception if the specified message ID represent a failure.
402+ """
202403 if message == MessageID .REPLY_SUCCESS :
203404 return # no error
204405 elif message == MessageID .REPLY_INVALID_MESSAGE :
@@ -209,23 +410,11 @@ def __check_empty_reply(self) -> bool:
209410 raise InvalidCRCError ()
210411 elif message == MessageID .REPLY_INVALID_PAYLOAD :
211412 raise InvalidPayloadError ()
413+ elif message == MessageID .REPLY_INVALID_VALUE :
414+ raise InvalidValueError ()
212415 else :
213416 raise InterfaceError ('Reply: Unknown reply?' )
214417
215- def __send (self , buffer : bytes ):
216- """
217- Transmits the specified buffer.
218- """
219- self .__validate_serial ()
220- self .serial .write (buffer )
221-
222- def __receive (self , size : int ):
223- """
224- Receives the specified number of bytes.
225- """
226- self .__validate_serial ()
227- return self .serial .read (size = size )
228-
229418 @staticmethod
230419 def __crc16 (buffer : bytes , seed : int = 0xFFFF ):
231420 """
0 commit comments