1- # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
2- # SPDX-FileCopyrightText: Copyright (c) 2021 Stefan Krüger for s-light
1+ # SPDX-FileCopyrightText: Copyright (c) 2021 Stefan Krüger s-light.eu
32#
43# SPDX-License-Identifier: MIT
54"""
1615
1716**Hardware:**
1817
19- .. todo:: Add links to any specific hardware product page(s), or category page(s).
20- Use unordered list & hyperlink rST inline format: "* `Link Text <url>`_"
21-
2218**Software and Dependencies:**
2319
2420* Adafruit CircuitPython firmware `>= 7.0.0 for the supported boards.
3834
3935
4036class NonBlockingSerialInput :
41- """docstring for NonBlockingSerialInput."""
37+ """Non Blocking Serial Input Class.
38+
39+ This CircuitPython helper class can be used as non-blocking *drop-in* for the build
40+ in ``input()`` method.
41+ And also as event / callback based handling.
42+ It implements the full input buffer handling and line-end parsing.
43+
44+ all parameters are keyword parameters.
45+
46+ :param function input_handling_fn: function to call if there is one ore more
47+ fully received new lines. ``input_handling(input_string: string)``
48+ Default: None
49+ :param function print_help_fn:function to call when a help text should be printed
50+ fully received new lines. ``print_help()``
51+ Default: None
52+ :param ~usb_cdc.Serial serial: serial connection object to use
53+ Default: usb_cdc.console
54+ :param bool echo: enable/disable remote echo
55+ Default: True
56+ :param bool statusline: enable/disable status line handling - `Not implemented yet - issue #1:
57+ <https://github.com/s-light/CircuitPython_nonblocking_serialinput/issues/1>`_
58+ Default: False
59+ :param string encoding: input string encoding
60+ Default: "utf-8"
61+ :param string, list line_end_custom: set custom line ends
62+ Default: None
63+ :param bool use_universal_line_end_basic: use a basic default set of line_ends
64+ [`\n `, '\r ', '\r \n ']
65+ Default: True
66+ :param bool use_universal_line_end_advanced: use a advanced default set of line_ends
67+ ['\v ', '\f ', '\x1c ',...]
68+ Default: False
69+ :param bool verbose: print debugging information in some internal functions. Default to False
70+
71+ """
4272
4373 def __init__ (
4474 self ,
@@ -52,6 +82,7 @@ def __init__(
5282 line_end_custom = None ,
5383 use_universal_line_end_basic = True ,
5484 use_universal_line_end_advanced = False ,
85+ verbose = False ,
5586 ):
5687 super ()
5788 self .input_handling_fn = input_handling_fn
@@ -67,6 +98,7 @@ def __init__(
6798 self .line_end_list .extend (universal_line_end_basic )
6899 if use_universal_line_end_advanced :
69100 self .line_end_list .extend (universal_line_end_advanced )
101+ self .verbose = verbose
70102
71103 # no block:
72104 self .serial .timeout = 0
@@ -96,30 +128,28 @@ def _buffer_endswith_line_end(self):
96128
97129 def _buffer_check_and_handle_line_ends (self ):
98130 if self ._buffer_count_line_ends ():
99- # we have at minimum one full command.
100- # let us extract this.
101- # buffer_endswith = self._buffer_endswith_line_end()
102- # if buffer_endswith:
103- # # just split the lines and all is fine..
104- # pass
105- # else:
106- # # we have to leave the last part in place..
107- # pass
108- # self.input_list.extend(self.input_buffer.splitlines(self.line_end_list))
109131 lines , rest = splitlines_advanced (self .input_buffer , self .line_end_list )
110- # print("lines: {}; rest: {}".format(repr(lines), repr(rest)))
111132 self .input_list .extend (lines )
112- # print("self.input_list: {}".format(repr(self.input_list)))
133+ if self .verbose :
134+ print ("lines: {}; rest: {}" .format (repr (lines ), repr (rest )))
135+ print ("self.input_list: {}" .format (repr (self .input_list )))
113136 if rest :
114137 self .input_buffer = rest
115138 else :
116139 self .input_buffer = ""
117140
118141 def input (self ):
119- """get oldest input string if there is any available. Otherwise an emtpy string."""
142+ """
143+ Input.
144+
145+ get oldest input string if there is any available. Otherwise an emtpy string.
146+
147+ :return string: if available oldest input_line. Otherwise `""`
148+ """
120149 try :
121150 result = self .input_list .pop (0 )
122- # print("result: {}".format(repr(result)))
151+ if self .verbose :
152+ print ("result: {}" .format (repr (result )))
123153 except IndexError :
124154 result = None
125155 return result
@@ -132,11 +162,10 @@ def update(self):
132162 raw = self .serial .read (available )
133163 if self .echo and not self .statusline :
134164 self .serial .write (raw )
135- self .input_buffer += raw .decode (
136- self .encoding ,
137- # encoding=self.encoding,
138- # errors="strict",
139- )
165+ self .input_buffer += raw .decode (self .encoding )
166+ # decode: keyword argeuments and errors not supported by CircuitPython
167+ # encoding=self.encoding,
168+ # errors="strict",
140169 self ._buffer_check_and_handle_line_ends ()
141170 available = self .serial .in_waiting
142171 parsed_input = False
@@ -153,8 +182,13 @@ def update(self):
153182##########################################
154183# helper
155184
156- # source for universal_line_end
157- # https://docs.python.org/3.8/library/stdtypes.html#str.splitlines
185+ """
186+ source for universal_line_end
187+ https://docs.python.org/3.8/library/stdtypes.html#str.splitlines
188+
189+ :attribute list: universal_line_end_basic
190+ :attribute list: universal_line_end_advanced
191+ """
158192universal_line_end_basic = [
159193 # Line Feed
160194 "\n " ,
@@ -204,9 +238,15 @@ def update(self):
204238# return result
205239
206240
241+ # def find_first_line_end(input_string, line_end_list=None, start=0, return_line_end=False):
207242def find_first_line_end (input_string , line_end_list = None , start = 0 ):
208243 """
209- Find first line_end in input_string.
244+ Find first line_end from line_end_list in input_string.
245+
246+ :param string input_string: input search
247+ :param list line_end_list: list with strings to search for.
248+ :param int start: start position for search. (default = 0)
249+ :return int: index of first found line_end; `-1` if nothing is found.
210250 """
211251 result = None
212252 if line_end_list is None :
@@ -228,10 +268,20 @@ def find_first_line_end(input_string, line_end_list=None, start=0):
228268def splitlines_advanced (input_string , line_end_list = None ):
229269 """
230270 Split lines in input_string at all line_ends in line_end_list.
271+
272+ This function searches for the all occurenc of all of strings in line_end_list.
273+ then splits at these points. the resulting list is returned.
274+ this also returns empty string segments.
275+ the search happens in the order of line_end_list.
276+ if the string does not end with a line_end symbol this last part will be returned in `rest`
277+
278+ :param string input_string: input to split
279+ :param list line_end_list: list with strings where the splitting should happen.
280+ :return tuple: Tuple (result_list, rest);
231281 """
232282 if line_end_list is None :
233283 line_end_list = universal_line_end_basic
234- result = []
284+ result_list = []
235285 rest = None
236286 # we have to do the splitting manually as we have a list of available seperators..
237287 pos_last = 0
@@ -240,7 +290,7 @@ def splitlines_advanced(input_string, line_end_list=None):
240290 ) > - 1 :
241291 # print("pos: {}".format(repr(pos)))
242292 # print("input_string[pos_last:pos]: {}".format(repr(input_string[pos_last:pos])))
243- result .append (input_string [pos_last :pos ])
293+ result_list .append (input_string [pos_last :pos ])
244294 pos_last = pos + 1
245295 # print("pos_last: {}".format(repr(pos_last)))
246296 # print("pos_last: {}".format(repr(pos_last)))
@@ -249,7 +299,7 @@ def splitlines_advanced(input_string, line_end_list=None):
249299 # print(" rest handling:")
250300 # print("input_string[pos_last:]: {}".format(repr(input_string[pos_last:])))
251301 rest = input_string [pos_last :]
252- return (result , rest )
302+ return (result_list , rest )
253303
254304
255305"""
@@ -308,6 +358,9 @@ def is_number(value):
308358
309359 based on
310360 https://stackoverflow.com/questions/354038/how-do-i-check-if-a-string-is-a-number-float
361+
362+ :param string value: input to check
363+ :return bool: True if value is a number, otherwise False.
311364 """
312365 try :
313366 float (value )
0 commit comments