Browse Source

Linting and tests

Peter Magnusson 8 years ago
parent
commit
acfe2dacc8

+ 2 - 1
.gitignore

@@ -1,4 +1,4 @@
-# python-stuff 
+# python-stuff
 *.egg-info/
 *.egg-info/
 .eggs
 .eggs
 dist/
 dist/
@@ -8,6 +8,7 @@ build/
 # testing
 # testing
 .coverage
 .coverage
 env/
 env/
+*.log
 
 
 # editors
 # editors
 .vscode/
 .vscode/

File diff suppressed because it is too large
+ 0 - 61
.pylintrc


+ 17 - 0
doc/TEST.md

@@ -0,0 +1,17 @@
+Testing nodemcu-uploader
+========================
+
+```
+pip install -r test_requirements.txt
+coverage run setup.py test
+```
+
+To run tests that actually communicate with a device you
+will need to set the __SERIALPORT__ environment variable
+to the port where you have an device connected.
+
+Linux
+```
+export SERIALPORT=/dev/ttyUSB0
+```
+

+ 2 - 1
nodemcu-uploader.py

@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
-from lib import main
+"""makes it easier to run nodemcu-uploader from command line"""
+from nodemcu_uploader import main
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':

+ 1 - 1
nodemcu_uploader/__init__.py

@@ -5,4 +5,4 @@
 """Library and util for uploading files to NodeMCU version 0.9.4 and later"""
 """Library and util for uploading files to NodeMCU version 0.9.4 and later"""
 
 
 from .version import __version__
 from .version import __version__
-from .uploader import Uploader
+from .uploader import Uploader

+ 6 - 3
nodemcu_uploader/exceptions.py

@@ -1,10 +1,13 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
+#pylint: disable=C0111
+
+"""Various custom exceptions"""
 
 
 class CommunicationTimeout(Exception):
 class CommunicationTimeout(Exception):
-    def __init__(self, message, buffer):
+    def __init__(self, message, buf):
         super(CommunicationTimeout, self).__init__(message)
         super(CommunicationTimeout, self).__init__(message)
-        self.buffer = buffer
+        self.buf = buf
 
 
 
 
 class BadResponseException(Exception):
 class BadResponseException(Exception):
@@ -17,4 +20,4 @@ class BadResponseException(Exception):
 
 
 
 
 class DeviceNotFoundException(Exception):
 class DeviceNotFoundException(Exception):
-    pass
+    pass

+ 5 - 4
nodemcu_uploader/luacode.py

@@ -1,11 +1,12 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+"""This module contains all the LUA code that needs to be on the device
+to perform whats needed. They will be uploaded if they doesn't exist"""
+
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
+# pylint: disable=C0301
 
 
 
 
-# these functions are needed on the device, otherwise they will be
-# uploaded during prepare
-LUA_FUNCTIONS = ['recv_block', 'recv_name','recv','shafile', 'send_block', 'send_file', 'send']
+LUA_FUNCTIONS = ['recv_block', 'recv_name', 'recv', 'shafile', 'send_block', 'send_file', 'send']
 
 
 DOWNLOAD_FILE = "file.open('{filename}') print(file.seek('end', 0)) file.seek('set', {bytes_read}) uart.write(0, file.read({chunk_size}))file.close()"
 DOWNLOAD_FILE = "file.open('{filename}') print(file.seek('end', 0)) file.seek('set', {bytes_read}) uart.write(0, file.read({chunk_size}))file.close()"
 
 

+ 34 - 28
nodemcu_uploader/main.py

@@ -1,7 +1,9 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 
 
+"""This module is the cli for the Uploader class"""
+
+
 import argparse
 import argparse
 import logging
 import logging
 import os
 import os
@@ -10,7 +12,7 @@ from .uploader import Uploader
 from .term import terminal
 from .term import terminal
 from serial import VERSION as serialversion
 from serial import VERSION as serialversion
 
 
-log = logging.getLogger(__name__)
+log = logging.getLogger(__name__) # pylint: disable=C0103
 from .version import __version__
 from .version import __version__
 
 
 def destination_from_source(sources):
 def destination_from_source(sources):
@@ -23,17 +25,17 @@ def destination_from_source(sources):
     destinations = []
     destinations = []
     newsources = []
     newsources = []
     for i in range(0, len(sources)):
     for i in range(0, len(sources)):
-        sd = sources[i].split(':')
-        if len(sd) == 2:
-            destinations.append(sd[1])
-            newsources[i] = sd[0]
+        srcdst = sources[i].split(':')
+        if len(srcdst) == 2:
+            destinations.append(srcdst[1])
+            newsources[i] = srcdst[0]
         else:
         else:
-            listing = glob.glob(sd[0])
+            listing = glob.glob(srcdst[0])
             for filename in listing:
             for filename in listing:
                 newsources.append(filename)
                 newsources.append(filename)
                 #always use forward slash at destination
                 #always use forward slash at destination
                 destinations.append(filename.replace('\\', '/'))
                 destinations.append(filename.replace('\\', '/'))
-            
+
     return [newsources, destinations]
     return [newsources, destinations]
 
 
 
 
@@ -42,17 +44,18 @@ def operation_upload(uploader, sources, verify, do_compile, do_file, do_restart)
     sources, destinations = destination_from_source(sources)
     sources, destinations = destination_from_source(sources)
     if len(destinations) == len(sources):
     if len(destinations) == len(sources):
         if uploader.prepare():
         if uploader.prepare():
-            for f, d in zip(sources, destinations):
+            for filename, dst in zip(sources, destinations):
                 if do_compile:
                 if do_compile:
-                    uploader.file_remove(os.path.splitext(d)[0]+'.lc')
-                uploader.write_file(f, d, verify)
-                if do_compile and d != 'init.lua':
-                    uploader.file_compile(d)
-                    uploader.file_remove(d)
+                    uploader.file_remove(os.path.splitext(dst)[0]+'.lc')
+                uploader.write_file(filename, dst, verify)
+                #init.lua is not allowed to be compiled
+                if do_compile and dst != 'init.lua':
+                    uploader.file_compile(dst)
+                    uploader.file_remove(dst)
                     if do_file:
                     if do_file:
-                        uploader.file_do(os.path.splitext(d)[0]+'.lc')
+                        uploader.file_do(os.path.splitext(dst)[0]+'.lc')
                 elif do_file:
                 elif do_file:
-                    uploader.file_do(d)
+                    uploader.file_do(dst)
         else:
         else:
             raise Exception('Error preparing nodemcu for reception')
             raise Exception('Error preparing nodemcu for reception')
     else:
     else:
@@ -67,8 +70,8 @@ def operation_download(uploader, sources):
     """The download operation"""
     """The download operation"""
     destinations = destination_from_source(sources)
     destinations = destination_from_source(sources)
     if len(destinations) == len(sources):
     if len(destinations) == len(sources):
-        for f, d in zip(sources, destinations):
-            uploader.read_file(f, d)
+        for filename, dst in zip(sources, destinations):
+            uploader.read_file(filename, dst)
     else:
     else:
         raise Exception('You must specify a destination filename for each file you want to download.')
         raise Exception('You must specify a destination filename for each file you want to download.')
     log.info('All done!')
     log.info('All done!')
@@ -79,16 +82,16 @@ def operation_file(uploader, cmd, filename=''):
     if cmd == 'list':
     if cmd == 'list':
         uploader.file_list()
         uploader.file_list()
     if cmd == 'do':
     if cmd == 'do':
-        for f in filename:
-            uploader.file_do(f)
+        for path in filename:
+            uploader.file_do(path)
     elif cmd == 'format':
     elif cmd == 'format':
         uploader.file_format()
         uploader.file_format()
     elif cmd == 'remove':
     elif cmd == 'remove':
-        for f in filename:
-            uploader.file_remove(f)
+        for path in filename:
+            uploader.file_remove(path)
     elif cmd == 'print':
     elif cmd == 'print':
-        for f in filename:
-            uploader.file_print(f)
+        for path in filename:
+            uploader.file_print(path)
 
 
 
 
 
 
@@ -98,6 +101,7 @@ def arg_auto_int(value):
 
 
 
 
 def main_func():
 def main_func():
+    """Main function for cli"""
     parser = argparse.ArgumentParser(
     parser = argparse.ArgumentParser(
         description='NodeMCU Lua file uploader',
         description='NodeMCU Lua file uploader',
         prog='nodemcu-uploader'
         prog='nodemcu-uploader'
@@ -187,7 +191,9 @@ def main_func():
         'download',
         'download',
         help='Path to one or more files to be downloaded. Destination name will be the same as the file name.')
         help='Path to one or more files to be downloaded. Destination name will be the same as the file name.')
 
 
-    download_parser.add_argument('filename', nargs='+', help='Lua file to download. Use colon to give alternate destination.')
+    download_parser.add_argument('filename',
+        nargs='+',
+        help='Lua file to download. Use colon to give alternate destination.')
 
 
 
 
     file_parser = subparsers.add_parser(
     file_parser = subparsers.add_parser(
@@ -207,7 +213,7 @@ def main_func():
 
 
     node_parse.add_argument('ncmd', choices=('heap', 'restart'), help="heap=print heap memory, restart=restart nodemcu")
     node_parse.add_argument('ncmd', choices=('heap', 'restart'), help="heap=print heap memory, restart=restart nodemcu")
 
 
-    terminal_parser = subparsers.add_parser(
+    subparsers.add_parser(
         'terminal',
         'terminal',
         help='Run pySerials miniterm'
         help='Run pySerials miniterm'
     )
     )
@@ -238,8 +244,8 @@ def main_func():
 
 
     elif args.operation == 'exec':
     elif args.operation == 'exec':
         sources = args.filename
         sources = args.filename
-        for f in sources:
-            uploader.exec_file(f)
+        for path in sources:
+            uploader.exec_file(path)
 
 
     elif args.operation == 'file':
     elif args.operation == 'file':
         operation_file(uploader, args.cmd, args.filename)
         operation_file(uploader, args.cmd, args.filename)

+ 118 - 98
nodemcu_uploader/uploader.py

@@ -1,6 +1,6 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
+"""Main functionality for nodemcu-uploader"""
 
 
 import time
 import time
 import logging
 import logging
@@ -8,19 +8,21 @@ import hashlib
 import os
 import os
 import serial
 import serial
 
 
-from .exceptions import CommunicationTimeout, DeviceNotFoundException, BadResponseException
+from .exceptions import CommunicationTimeout, DeviceNotFoundException
+from .exceptions import BadResponseException
 from .utils import default_port, system
 from .utils import default_port, system
-from .luacode import DOWNLOAD_FILE, RECV_LUA, SEND_LUA, LUA_FUNCTIONS, LIST_FILES, UART_SETUP, PRINT_FILE
+from .luacode import LUA_FUNCTIONS, DOWNLOAD_FILE, RECV_LUA, SEND_LUA, LIST_FILES, UART_SETUP, PRINT_FILE
 
 
-log = logging.getLogger(__name__)
+log = logging.getLogger(__name__) # pylint: disable=C0103
 
 
 __all__ = ['Uploader', 'default_port']
 __all__ = ['Uploader', 'default_port']
 
 
 SYSTEM = system()
 SYSTEM = system()
 
 
-BLOCK_START='\x01';
-NUL = '\x00';
-ACK = '\x06';
+MINIMAL_TIMEOUT = 0.0001
+BLOCK_START = '\x01'
+NUL = '\x00'
+ACK = '\x06'
 
 
 class Uploader(object):
 class Uploader(object):
     """Uploader is the class for communicating with the nodemcu and
     """Uploader is the class for communicating with the nodemcu and
@@ -46,30 +48,31 @@ class Uploader(object):
         self._port.setRTS(False)
         self._port.setRTS(False)
         self._port.setDTR(False)
         self._port.setDTR(False)
 
 
-        def sync():
-            # Get in sync with LUA (this assumes that NodeMCU gets reset by the previous two lines)
-            log.debug('getting in sync with LUA');
-            self.clear_buffers()
+        def __sync():
+            """Get in sync with LUA (this assumes that NodeMCU gets reset by the previous two lines)"""
+            log.debug('getting in sync with LUA')
+            self.__clear_buffers()
             try:
             try:
-                self.exchange(';') # Get a defined state
-                self.writeln('print("%sync%");')
-                self.expect('%sync%\r\n> ')
+                self.__exchange(';') # Get a defined state
+                self.__writeln('print("%sync%");')
+                self.__expect('%sync%\r\n> ')
             except CommunicationTimeout:
             except CommunicationTimeout:
                 raise DeviceNotFoundException('Device not found or wrong port')
                 raise DeviceNotFoundException('Device not found or wrong port')
 
 
-        sync()
+        __sync()
 
 
         if baud != start_baud:
         if baud != start_baud:
-            self.set_baudrate(baud)
+            self.__set_baudrate(baud)
 
 
             # Get in sync again
             # Get in sync again
-            sync()
+            __sync()
 
 
         self.line_number = 0
         self.line_number = 0
 
 
-    def set_baudrate(self, baud):
+    def __set_baudrate(self, baud):
+        """setting baudrate if supported"""
         log.info('Changing communication to %s baud', baud)
         log.info('Changing communication to %s baud', baud)
-        self.writeln(UART_SETUP.format(baud=baud))
+        self.__writeln(UART_SETUP.format(baud=baud))
         # Wait for the string to be sent before switching baud
         # Wait for the string to be sent before switching baud
         time.sleep(0.1)
         time.sleep(0.1)
         try:
         try:
@@ -79,7 +82,8 @@ class Uploader(object):
             self._port.baudrate = baud
             self._port.baudrate = baud
 
 
 
 
-    def clear_buffers(self):
+    def __clear_buffers(self):
+        """Clears the input and output buffers"""
         try:
         try:
             self._port.reset_input_buffer()
             self._port.reset_input_buffer()
             self._port.reset_output_buffer()
             self._port.reset_output_buffer()
@@ -89,16 +93,14 @@ class Uploader(object):
             self._port.flushOutput()
             self._port.flushOutput()
 
 
 
 
-    def expect(self, exp='> ', timeout=TIMEOUT):
+    def __expect(self, exp='> ', timeout=TIMEOUT):
         """will wait for exp to be returned from nodemcu or timeout"""
         """will wait for exp to be returned from nodemcu or timeout"""
+        timer = self._port.timeout
         #do NOT set timeout on Windows
         #do NOT set timeout on Windows
         if SYSTEM != 'Windows':
         if SYSTEM != 'Windows':
-            timer = self._port.timeout
-
             # Checking for new data every 100us is fast enough
             # Checking for new data every 100us is fast enough
-            lt = 0.0001
-            if self._port.timeout != lt:
-                self._port.timeout = lt
+            if self._port.timeout != MINIMAL_TIMEOUT:
+                self._port.timeout = MINIMAL_TIMEOUT
 
 
         end = time.time() + timeout
         end = time.time() + timeout
 
 
@@ -119,7 +121,7 @@ class Uploader(object):
 
 
         return data
         return data
 
 
-    def write(self, output, binary=False):
+    def __write(self, output, binary=False):
         """write data on the nodemcu port. If 'binary' is True the debug log
         """write data on the nodemcu port. If 'binary' is True the debug log
         will show the intended output as hex, otherwise as string"""
         will show the intended output as hex, otherwise as string"""
         if not binary:
         if not binary:
@@ -129,23 +131,24 @@ class Uploader(object):
         self._port.write(output)
         self._port.write(output)
         self._port.flush()
         self._port.flush()
 
 
-    def writeln(self, output):
+    def __writeln(self, output):
         """write, with linefeed"""
         """write, with linefeed"""
-        self.write(output + '\n')
+        self.__write(output + '\n')
 
 
-    def exchange(self, output, timeout=TIMEOUT):
-        self.writeln(output)
+    def __exchange(self, output, timeout=TIMEOUT):
+        """Write output to the port and wait for response"""
+        self.__writeln(output)
         self._port.flush()
         self._port.flush()
-        return self.expect(timeout=timeout)
+        return self.__expect(timeout=timeout)
 
 
 
 
     def close(self):
     def close(self):
         """restores the nodemcu to default baudrate and then closes the port"""
         """restores the nodemcu to default baudrate and then closes the port"""
         try:
         try:
             if self.baud != self.start_baud:
             if self.baud != self.start_baud:
-                self.set_baudrate(self.start_baud)
+                self.__set_baudrate(self.start_baud)
             self._port.flush()
             self._port.flush()
-            self.clear_buffers()
+            self.__clear_buffers()
         except serial.serialutil.SerialException:
         except serial.serialutil.SerialException:
             pass
             pass
         log.debug('closing port')
         log.debug('closing port')
@@ -159,9 +162,9 @@ class Uploader(object):
         """
         """
         log.info('Preparing esp for transfer.')
         log.info('Preparing esp for transfer.')
 
 
-        for fn in LUA_FUNCTIONS:
-            d = self.exchange('print({0})'.format(fn))
-            if d.find('function:') == -1:
+        for func in LUA_FUNCTIONS:
+            detected = self.__exchange('print({0})'.format(func))
+            if detected.find('function:') == -1:
                 break
                 break
         else:
         else:
             log.info('Preparation already done. Not adding functions again.')
             log.info('Preparation already done. Not adding functions again.')
@@ -178,35 +181,36 @@ class Uploader(object):
             if len(line) == 0:
             if len(line) == 0:
                 continue
                 continue
 
 
-            d = self.exchange(line)
+            resp = self.__exchange(line)
             #do some basic test of the result
             #do some basic test of the result
-            if ('unexpected' in d) or ('stdin' in d) or len(d) > len(functions)+10:
-                log.error('error when preparing "%s"', d)
+            if ('unexpected' in resp) or ('stdin' in resp) or len(resp) > len(functions)+10:
+                log.error('error when preparing "%s"', resp)
                 return False
                 return False
         return True
         return True
 
 
     def download_file(self, filename):
     def download_file(self, filename):
-        res = self.exchange('send("{filename}")'.format(filename=filename))
+        """Download a file from device to local filesystem"""
+        res = self.__exchange('send("{filename}")'.format(filename=filename))
         if ('unexpected' in res) or ('stdin' in res):
         if ('unexpected' in res) or ('stdin' in res):
-            log.error('Unexpected error downloading file', res)
+            log.error('Unexpected error downloading file: %s', res)
             raise Exception('Unexpected error downloading file')
             raise Exception('Unexpected error downloading file')
 
 
         #tell device we are ready to receive
         #tell device we are ready to receive
-        self.write('C')
+        self.__write('C')
         #we should get a NUL terminated filename to start with
         #we should get a NUL terminated filename to start with
-        sent_filename = self.expect(NUL).strip()
+        sent_filename = self.__expect(NUL).strip()
         log.info('receiveing ' + sent_filename)
         log.info('receiveing ' + sent_filename)
 
 
         #ACK to start download
         #ACK to start download
-        self.write(ACK, True);
-        buffer = ''
+        self.__write(ACK, True)
+        buf = ''
         data = ''
         data = ''
-        chunk, buffer = self.read_chunk(buffer)
+        chunk, buf = self.__read_chunk(buf)
         #read chunks until we get an empty which is the end
         #read chunks until we get an empty which is the end
         while chunk != '':
         while chunk != '':
-            self.write(ACK,True);
+            self.__write(ACK, True)
             data = data + chunk
             data = data + chunk
-            chunk, buffer = self.read_chunk(buffer)
+            chunk, buf = self.__read_chunk(buf)
         return data
         return data
 
 
     def read_file(self, filename, destination=''):
     def read_file(self, filename, destination=''):
@@ -214,29 +218,31 @@ class Uploader(object):
             destination = filename
             destination = filename
         log.info('Transfering %s to %s', filename, destination)
         log.info('Transfering %s to %s', filename, destination)
         data = self.download_file(filename)
         data = self.download_file(filename)
-        with open(destination, 'w') as f:
-            f.write(data)
+        with open(destination, 'w') as fil:
+            fil.write(data)
 
 
     def write_file(self, path, destination='', verify='none'):
     def write_file(self, path, destination='', verify='none'):
+        """sends a file to the device using the transfer protocol"""
         filename = os.path.basename(path)
         filename = os.path.basename(path)
         if not destination:
         if not destination:
             destination = filename
             destination = filename
+
         log.info('Transfering %s as %s', path, destination)
         log.info('Transfering %s as %s', path, destination)
-        self.writeln("recv()")
+        self.__writeln("recv()")
 
 
-        res = self.expect('C> ')
+        res = self.__expect('C> ')
         if not res.endswith('C> '):
         if not res.endswith('C> '):
             log.error('Error waiting for esp "%s"', res)
             log.error('Error waiting for esp "%s"', res)
             return
             return
         log.debug('sending destination filename "%s"', destination)
         log.debug('sending destination filename "%s"', destination)
-        self.write(destination + '\x00', True)
-        if not self.got_ack():
+        self.__write(destination + '\x00', True)
+        if not self.__got_ack():
             log.error('did not ack destination filename')
             log.error('did not ack destination filename')
             return
             return
 
 
-        f = open(path, 'rb')
-        content = f.read()
-        f.close()
+        fil = open(path, 'rb')
+        content = fil.read()
+        fil.close()
 
 
         log.debug('sending %d bytes in %s', len(content), filename)
         log.debug('sending %d bytes in %s', len(content), filename)
         pos = 0
         pos = 0
@@ -247,16 +253,16 @@ class Uploader(object):
                 rest = chunk_size
                 rest = chunk_size
 
 
             data = content[pos:pos+rest]
             data = content[pos:pos+rest]
-            if not self.write_chunk(data):
-                d = self.expect()
-                log.error('Bad chunk response "%s" %s', d, ':'.join(x.encode('hex') for x in d))
+            if not self.__write_chunk(data):
+                resp = self.__expect()
+                log.error('Bad chunk response "%s" %s', resp, ':'.join(x.encode('hex') for x in resp))
                 return
                 return
 
 
             pos += chunk_size
             pos += chunk_size
 
 
         log.debug('sending zero block')
         log.debug('sending zero block')
         #zero size block
         #zero size block
-        self.write_chunk('')
+        self.__write_chunk('')
 
 
         if verify == 'raw':
         if verify == 'raw':
             log.info('Verifying...')
             log.info('Verifying...')
@@ -267,7 +273,7 @@ class Uploader(object):
                 log.info('Verification successfull. Contents are identical.')
                 log.info('Verification successfull. Contents are identical.')
         elif verify == 'sha1':
         elif verify == 'sha1':
             #Calculate SHA1 on remote file. Extract just hash from result
             #Calculate SHA1 on remote file. Extract just hash from result
-            data = self.exchange('shafile("'+destination+'")').splitlines()[1]
+            data = self.__exchange('shafile("'+destination+'")').splitlines()[1]
             log.info('Remote SHA1: %s', data)
             log.info('Remote SHA1: %s', data)
 
 
             #Calculate hash of local data
             #Calculate hash of local data
@@ -283,24 +289,26 @@ class Uploader(object):
 
 
 
 
     def exec_file(self, path):
     def exec_file(self, path):
+        """execute the lines in the local file 'path'"""
         filename = os.path.basename(path)
         filename = os.path.basename(path)
         log.info('Execute %s', filename)
         log.info('Execute %s', filename)
 
 
-        f = open(path, 'rt')
+        fil = open(path, 'r')
 
 
         res = '> '
         res = '> '
-        for line in f:
+        for line in fil:
             line = line.rstrip('\r\n')
             line = line.rstrip('\r\n')
-            retlines = (res + self.exchange(line)).splitlines()
+            retlines = (res + self.__exchange(line)).splitlines()
             # Log all but the last line
             # Log all but the last line
             res = retlines.pop()
             res = retlines.pop()
             for lin in retlines:
             for lin in retlines:
                 log.info(lin)
                 log.info(lin)
         # last line
         # last line
         log.info(res)
         log.info(res)
-        f.close()
+        fil.close()
 
 
-    def got_ack(self):
+    def __got_ack(self):
+        """Returns true if ACK is received"""
         log.debug('waiting for ack')
         log.debug('waiting for ack')
         res = self._port.read(1)
         res = self._port.read(1)
         log.debug('ack read %s', res.encode('hex'))
         log.debug('ack read %s', res.encode('hex'))
@@ -311,12 +319,14 @@ class Uploader(object):
         lines = data.replace('\r', '').split('\n')
         lines = data.replace('\r', '').split('\n')
 
 
         for line in lines:
         for line in lines:
-            self.exchange(line)
+            self.__exchange(line)
 
 
         return
         return
 
 
 
 
-    def write_chunk(self, chunk):
+    def __write_chunk(self, chunk):
+        """formats and sends a chunk of data to the device according
+        to transfer protocol"""
         log.debug('writing %d bytes chunk', len(chunk))
         log.debug('writing %d bytes chunk', len(chunk))
         data = BLOCK_START + chr(len(chunk)) + chunk
         data = BLOCK_START + chr(len(chunk)) + chunk
         if len(chunk) < 128:
         if len(chunk) < 128:
@@ -324,87 +334,97 @@ class Uploader(object):
             log.debug('pad with %d characters', padding)
             log.debug('pad with %d characters', padding)
             data = data + (' ' * padding)
             data = data + (' ' * padding)
         log.debug("packet size %d", len(data))
         log.debug("packet size %d", len(data))
-        self.write(data)
+        self.__write(data)
         self._port.flush()
         self._port.flush()
-        return self.got_ack()
+        return self.__got_ack()
 
 
 
 
-    def read_chunk(self, buffer):
+    def __read_chunk(self, buf):
+        """Read a chunk of data"""
         log.debug('reading chunk')
         log.debug('reading chunk')
         timeout = self._port.timeout
         timeout = self._port.timeout
         if SYSTEM != 'Windows':
         if SYSTEM != 'Windows':
-            timer = self._port.timeout
             # Checking for new data every 100us is fast enough
             # Checking for new data every 100us is fast enough
-            lt = 0.0001
-            if self._port.timeout != lt:
-                self._port.timeout = lt
+            if self._port.timeout != MINIMAL_TIMEOUT:
+                self._port.timeout = MINIMAL_TIMEOUT
 
 
         end = time.time() + timeout
         end = time.time() + timeout
 
 
-        while len(buffer) < 130 and time.time() <= end:
-            buffer = buffer + self._port.read()
+        while len(buf) < 130 and time.time() <= end:
+            buf = buf + self._port.read()
 
 
-        if buffer[0] != BLOCK_START or len(buffer) < 130:
-            print 'buffer size:', len(buffer)
-            log.debug('buffer binary: ', ':'.join(x.encode('hex') for x in buffer))
+        if buf[0] != BLOCK_START or len(buf) < 130:
+            print 'buffer size:', len(buf)
+            log.debug('buffer binary: %s ', ':'.join(x.encode('hex') for x in buf))
             raise Exception('Bad blocksize or start byte')
             raise Exception('Bad blocksize or start byte')
 
 
-        chunk_size = ord(buffer[1])
-        data = buffer[2:chunk_size+2]
-        buffer = buffer[130:]
-        return (data, buffer)
+        if SYSTEM != 'Windows':
+            self._port.timeout = timeout
+
+        chunk_size = ord(buf[1])
+        data = buf[2:chunk_size+2]
+        buf = buf[130:]
+        return (data, buf)
 
 
 
 
     def file_list(self):
     def file_list(self):
+        """list files on the device"""
         log.info('Listing files')
         log.info('Listing files')
-        res = self.exchange(LIST_FILES)
+        res = self.__exchange(LIST_FILES)
         log.info(res)
         log.info(res)
         return res
         return res
 
 
-    def file_do(self, f):
-        log.info('Executing '+f)
-        res = self.exchange('dofile("'+f+'")')
+    def file_do(self, filename):
+        """Execute a file on the device using 'do'"""
+        log.info('Executing '+filename)
+        res = self.__exchange('dofile("'+filename+'")')
         log.info(res)
         log.info(res)
         return res
         return res
 
 
     def file_format(self):
     def file_format(self):
+        """Formats device filesystem"""
         log.info('Formating, can take up to 1 minute...')
         log.info('Formating, can take up to 1 minute...')
-        res = self.exchange('file.format()', timeout=60)
+        res = self.__exchange('file.format()', timeout=60)
         if 'format done' not in res:
         if 'format done' not in res:
             log.error(res)
             log.error(res)
         else:
         else:
             log.info(res)
             log.info(res)
         return res
         return res
 
 
-    def file_print(self, f):
-        log.info('Printing ' + f)
-        res = self.exchange(PRINT_FILE.format(filename=f))
+    def file_print(self, filename):
+        """Prints a file on the device to console"""
+        log.info('Printing ' + filename)
+        res = self.__exchange(PRINT_FILE.format(filename=filename))
         log.info(res)
         log.info(res)
         return res
         return res
 
 
     def node_heap(self):
     def node_heap(self):
+        """Show device heap size"""
         log.info('Heap')
         log.info('Heap')
-        res = self.exchange('print(node.heap())')
+        res = self.__exchange('print(node.heap())')
         log.info(res)
         log.info(res)
         return res
         return res
 
 
     def node_restart(self):
     def node_restart(self):
+        """Restarts device"""
         log.info('Restart')
         log.info('Restart')
-        res = self.exchange('node.restart()')
+        res = self.__exchange('node.restart()')
         log.info(res)
         log.info(res)
         return res
         return res
 
 
     def file_compile(self, path):
     def file_compile(self, path):
+        """Compiles a file specified by path on the device"""
         log.info('Compile '+path)
         log.info('Compile '+path)
         cmd = 'node.compile("%s")' % path
         cmd = 'node.compile("%s")' % path
-        res = self.exchange(cmd)
+        res = self.__exchange(cmd)
         log.info(res)
         log.info(res)
         return res
         return res
 
 
     def file_remove(self, path):
     def file_remove(self, path):
+        """Removes a file on the device"""
         log.info('Remove '+path)
         log.info('Remove '+path)
         cmd = 'file.remove("%s")' % path
         cmd = 'file.remove("%s")' % path
-        res = self.exchange(cmd)
+        res = self.__exchange(cmd)
         log.info(res)
         log.info(res)
         return res
         return res
 
 

+ 3 - 3
nodemcu_uploader/utils.py

@@ -1,15 +1,15 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
+"""Various utility functions"""
 
 
 from platform import system
 from platform import system
 from os import environ
 from os import environ
 
 
 __all__ = ['default_port', 'system']
 __all__ = ['default_port', 'system']
 
 
-def default_port(sysname = system()):
+def default_port(sysname=system()):
     """This returns the default port used for different systems if SERIALPORT env variable is not set"""
     """This returns the default port used for different systems if SERIALPORT env variable is not set"""
-    system_default =  {
+    system_default = {
         'Windows': 'COM1',
         'Windows': 'COM1',
         'Darwin': '/dev/tty.SLAB_USBtoUART'
         'Darwin': '/dev/tty.SLAB_USBtoUART'
     }.get(sysname, '/dev/ttyUSB0')
     }.get(sysname, '/dev/ttyUSB0')

+ 2 - 1
nodemcu_uploader/version.py

@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
+"""just keeper of current version"""
 
 
-#remember to update tests when version changes
+#TODO: remember to update tests when version changes
 __version__ = '0.3.0'
 __version__ = '0.3.0'

+ 16 - 0
pylintrc

@@ -0,0 +1,16 @@
+[MASTER]
+persistent=yes
+
+[FORMAT]
+# Maximum number of characters on a single line.
+max-line-length=120
+
+[DESIGN]
+max-args=6
+
+[MESSAGES CONTROL]
+disable=I0011
+
+[REPORTS]
+msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
+include-ids=yes

+ 5 - 3
setup.py

@@ -1,11 +1,13 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
+"""Setup for nodemcu-uploader"""
+
 from setuptools import setup
 from setuptools import setup
 
 
-exec(open('nodemcu_uploader/version.py').read())
+exec(open('nodemcu_uploader/version.py').read()) #pylint: disable=W0122
 
 
 setup(name='nodemcu-uploader',
 setup(name='nodemcu-uploader',
-      version=__version__,
+      version=__version__, #pylint: disable=E0602
       install_requires=[
       install_requires=[
           'pyserial>=2.7'
           'pyserial>=2.7'
       ],
       ],
@@ -22,7 +24,7 @@ setup(name='nodemcu-uploader',
           'Programming Language :: Python :: 2.7'
           'Programming Language :: Python :: 2.7'
       ],
       ],
       license='MIT',
       license='MIT',
-      test_suite = "tests.get_tests",
+      test_suite="tests.get_tests",
       entry_points={
       entry_points={
           'console_scripts': [
           'console_scripts': [
               'nodemcu-uploader=nodemcu_uploader.main:main_func'
               'nodemcu-uploader=nodemcu_uploader.main:main_func'

+ 6 - 3
tests/__init__.py

@@ -1,16 +1,19 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 import unittest
 import unittest
+import logging
 
 
 def get_tests():
 def get_tests():
     return full_suite()
     return full_suite()
 
 
 def full_suite():
 def full_suite():
+    logging.basicConfig(filename='test-debug.log', level=logging.INFO, format='%(message)s')
+
     from .misc import MiscTestCase
     from .misc import MiscTestCase
-    #from .uploader import UploaderTestCase
+    from .uploader import UploaderTestCase
     # from .serializer import ResourceTestCase as SerializerTestCase
     # from .serializer import ResourceTestCase as SerializerTestCase
     # from .utils import UtilsTestCase
     # from .utils import UtilsTestCase
 
 
     miscsuite = unittest.TestLoader().loadTestsFromTestCase(MiscTestCase)
     miscsuite = unittest.TestLoader().loadTestsFromTestCase(MiscTestCase)
-    #uploadersuite = unittest.TestLoader().loadTestsFromTestCase(UploaderTestCase)
-    return unittest.TestSuite([miscsuite, ])
+    uploadersuite = unittest.TestLoader().loadTestsFromTestCase(UploaderTestCase)
+    return unittest.TestSuite([miscsuite, uploadersuite])

+ 22 - 5
tests/uploader.py

@@ -1,15 +1,32 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 # Copyright (C) 2015-2016 Peter Magnusson <peter@birchroad.net>
 import unittest
 import unittest
+import os, time
 from nodemcu_uploader import Uploader, __version__
 from nodemcu_uploader import Uploader, __version__
 
 
+LOOPPORT='loop://'
+
 #on which port should the tests be performed
 #on which port should the tests be performed
-PORT = 'loop://'
+SERIALPORT = os.environ.get('SERIALPORT', LOOPPORT)
 
 
-#which speed should the tests use
-BAUD = 115200
 
 
 class UploaderTestCase(unittest.TestCase):
 class UploaderTestCase(unittest.TestCase):
+    uploader = None
+    def setUp(self):
+        self.uploader =  Uploader(SERIALPORT)
+
+    def tearDown(self):
+        self.uploader.node_restart()
+        self.uploader.close()
+        time.sleep(1)
+
+    # def test_initialize(self):
+    #     print SERIALPORT
+
+
+
+    @unittest.skipUnless(SERIALPORT <>  LOOPPORT, 'Needs a configured SERIALPORT')
+    def test_upload_and_verify_raw(self):
+        self.uploader.prepare()
+        self.uploader.write_file('tests/fixtures/big_file.txt', verify='raw')
 
 
-    def test_initialize(self):
-        uploader = Uploader(PORT, BAUD)

+ 2 - 2
tests/winserial.py

@@ -9,7 +9,7 @@ import time
 SERIALPORT = os.environ.get('SERIALPORT', default_port())
 SERIALPORT = os.environ.get('SERIALPORT', default_port())
 
 
 def expect(port, timeout, exp='> '):
 def expect(port, timeout, exp='> '):
-    timer = port.timeout
+    timeout = port.timeout
     # lt = 0.0001
     # lt = 0.0001
     # if port.timeout != lt:
     # if port.timeout != lt:
     #     port.timeout = lt
     #     port.timeout = lt
@@ -19,7 +19,7 @@ def expect(port, timeout, exp='> '):
     while not data.endswith(exp) and time.time() <= end:
     while not data.endswith(exp) and time.time() <= end:
         data += port.read()
         data += port.read()
 
 
-    # port.timeout = timer
+    # port.timeout = timeout
 
 
     return data
     return data
 
 

Some files were not shown because too many files changed in this diff