[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n[*.py]\nindent_style = space\nindent_size = 4\ncharset = utf-8\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".gitignore",
    "content": "# python-stuff\n*.egg-info/\n.eggs\ndist/\nbuild/\n*.pyc\n__pycache__/\n\n# testing\n.coverage\nenv/\nenv3/\n*.log\ntmp/\nvenv/\n\n# editors\n.vscode/\n.tox\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\npython:\n  - 3.6\n  - 3.7\n\n# command to install dependencies\ninstall: \"pip install -r test_requirements.txt\"\n\n# command to run tests\nscript:\n  - coverage run setup.py test\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (C) 2015-2020 Peter Magnusson <me@kmpm.se>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "nodemcu-uploader.py\n===================\n\n__Archival notice!!!__ This project is currently archived because of lack of time\nand interest. I no longer use NodeMCU in any form. But someone else might be.\nIf you want to post a notice here about your active alternative then send me a\nDM here on github.\n\n---\n\nA simple tool for uploading files to the filesystem of an\nESP8266 running NodeMCU as well as some other useful commands.\n\nIt should work on Linux, and OS X; and with any type of file\nthat fits the filesystem, binary or text.\n\n| master |\n|--------|\n|[![Build Status](https://travis-ci.org/kmpm/nodemcu-uploader.svg?branch=master)](https://travis-ci.org/kmpm/nodemcu-uploader) | \n\nPlease note that these tests is not complete and it might be the tests\nthemselves that are having issues.\n\n\nCall for maintainers\n--------------------\nHi,\nThis project is in need of maintenance and I (kmpm) do not have the time the \nproject deserves. Look at https://github.com/kmpm/nodemcu-uploader/issues/90 \nfor more information on what to do about it or email me@kmpm.se\n\n\nInstallation\n-------------\nShould be installable by PyPI (prefered) but there might be\npackaging issues still.\n\n    pip install nodemcu-uploader\n    nodemcu-uploader\n\nOtherwise clone from github and run directly from there\n\n    git clone https://github.com/kmpm/nodemcu-uploader\n    cd nodemcu-uploader\n    python ./nodemcu-uploader.py\n\nNote that pip would install pyserial >= 2.7.\nThe terminal command (using miniterm from pyserial) might\nnot work depending on version used. This is a known issue.\n\n\n### Notes for Windows\nThere might be some\n[significant issues with Windows](https://github.com/kmpm/nodemcu-uploader/issues?q=is%3Aissue+is%3Aopen+label%3Aos%3Awindows).\n\n### Notes for OS X\nTo solve \"ImportError: No module named serial\", install the pyserial module:\n```sh\npython easy_install pyserial\n```\n\nUsage\n-----\nDownload NodeMCU firmware from  http://nodemcu-build.com/ .\n\nSince version v0.4.0 of the tool you will need a recent (june/july 2016) version \nof the firmware for nodemcu. The default baudrate was changed in firmware from\n9600 to 115200 and this tool was changed as well. \n\nIf you are using an older firmware you MUST use the option `--start-baud 9600`\nto the device to be recognized. Otherwise you will get a \n_Device not found or wrong port_ error.\n\nFor more usage details see [USAGE.md in doc](doc/USAGE.md)\n\n\nIssues\n-------\nWhen reporting issues please provide operating system (windows, mac, linux etc.),\nversion of this tool `nodemcu-uploader --version` and version of the firmware\non you device. If you got the firmware from http://nodemcu-build.com/ please\ntell if it was the dev or master branch and at what date it was created.\n\nAs for firmware version I would like to have a dump of the details you get\nwhen connected using a terminal to the device at boot time.\nIt would look something like this...\n```\nNodeMCU custom build by frightanic.com\n        branch: master\n        commit: b580bfe79e6e73020c2bd7cd92a6afe01a8bc867\n        SSL: false\n        modules: crypto,file,gpio,http,mdns,mqtt,net,node,tmr,uart,wifi\n build  built on: 2016-07-29 11:08\n powered by Lua 5.1.4 on SDK 1.5.1(e67da894)\n ```\n\nWhen you have as much of that as possible, \ncreate a issue in github, https://github.com/kmpm/nodemcu-uploader/issues\n\n\n\nTechnical Details\n-----------------\nThis *almost* uses a implementation of xmodem protocol for the up-/download part.\nThe main missing part is checksum and retransmission.\n\nThis is made possible by first preparing the device by creating a set of helper\nfunctions using the ordinary terminal mode.\nThese function utilize the built in uart module for the actual transfer and\ncuts up the transfers to a set of manageable blocks that are reassembled\nin the receiving end.\n\n### Upload\n1. Client calls the function recv()\n2. NodeMCU disables echo and send a 'C' to tell that it's ready to receive data\n3. Client sends a filename terminated with 0x00\n4. NodeMCU sends ACK\n5. Client send block of data according to the definition.\n6. NodeMCU sends ACK\n7. Step 5 and 6 are repeated until NodeMCU receives a block with 0 as size.\n8. NodeMCU enables normal terminal again with echo\n\n### Download\n1. Client calls the function send(<filename>).\n2. NodeMCU disables echo and waits for start.\n2. Client send a 'C' to tell that it's ready to receive data\n3. NodeMCU sends a filename terminated with 0x00\n4. Client sends ACK\n5. NodeMCU send block of data according to the definition.\n6. Client sends ACK\n7. Step 5 and 6 are repeated until client receives a block with 0 as size.\n8. NodeMCU enables normal terminal again with echo.\n\n\n\n### Data Block Definition\n__SOH__, __size__, __data[128]__\n\n* SOH = 0x01\n* Single byte telling how much of the 128 bytes data that are actually used.\n* Data padded with random bytes to fill out the 128 bytes frame.\n\nThis gives a total 130 bytes per block.\n\nThe block size was decided for...\n\n1. Being close to xmodem from where the inspiration came\n2. A fixed size allow the use of the uart.on('data') event very easy.\n3. 130 bytes would fit in the receive buffer.\n4. It would not waste that much traffic if the total size uploaded was not a \n   even multiple of the allowed datasize.\n\n\n\nDisclaimer\n-----------\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "doc/DEVELOP.md",
    "content": "Develop and Test nodemcu-uploader\n=================================\n\nConfigure development environment\n-------\n```shell\ngit clone https://github.com/kmpm/nodemcu-uploader\ncd nodemcu-uploader\npython3 -m venv\n. venv/bin/activate\npip install -r test_requirements.txt\npip install -e .\n```\n\n\n\nTesting\n-------\n```shell\npip install -r test_requirements.txt\ncoverage run setup.py test\n# or even better testing with tox\ntox\n```\n\nTo run tests that actually communicate with a device you\nwill need to set the __SERIALPORT__ environment variable\nto the port where you have an device connected.\n\nLinux\n```\nexport SERIALPORT=/dev/ttyUSB0\n```\n\n\nPublishing\n----------\n* https://packaging.python.org/tutorials/packaging-projects/\n\nPlease make sure to bump the version number in\nnodemcu_uploader/version.py as well as the testing of that\nnumber in tests/misc.py\n\n```bash\n#\npython -m pip install --upgrade setuptools wheel twine\npython setup.py sdist bdist_wheel\n\n#test upload\npython -m twine upload -u __token__ --repository testpypi dist/*\n\n#real upload\npython -m twine upload -u __token__ dist/*\n```\n"
  },
  {
    "path": "doc/USAGE.md",
    "content": " Usage\r\n===================\r\nThis document is by no means complete.\r\n\r\n\r\n## Common options\r\n* --help will show some help\r\n* --start_baud set at a default of 115200 (the speed of the nodemcu at boot in later\r\n  versions of the firmware)\r\n* --baud are set at a default of 115200. This setting is used for transfers and such.\r\n* --port is by default __/dev/ttyUSB0__,\r\n  __/dev/tty.SLAB_USBtoUART__ if on Mac and __COM1__ on Windows\r\n* the environment variable __SERIALPORT__ will override any default port\r\n\r\n\r\nSince version v0.4.0 of the tool you will need a recent (june/july 2016) \r\nversion or later of the firmware for nodemcu. The default baudrate was changed in \r\nfirmware from 9600 to 115200 and this tool was changed as well. \r\nDownload a recent firmware from http://nodemcu-build.com/ .\r\n\r\nSince v0.2.1 the program works with 2 possible speeds. It connects at a default\r\n(--start_baud) of 115200 baud which is what the default firmware uses. Earlier\r\nversions of the firmware and this tool used 9600 as start baudrate.\r\nImmediately after first established communication it changes\r\nto a higher (--baud) speed, if neccesary, which defaults\r\nto 115200. This allows all communication to happen much faster without having to\r\nrecompile the firmware or do any manual changes to the speed.\r\nWhen done and before it closes the port it changes the speed back to normal\r\nif it was changed.\r\n\r\nSince v0.4.0 of nodemcu-uploader it tries to use the auto-baudrate feature\r\nbuild in to the firmware by sending a character repetedly when initiating\r\ncommunication. This requires a firmware from june/july 2016 or later.\r\n\r\n## Commands\r\n### Upload\r\nFrom computer to esp device.\r\n\r\n```\r\nnodemcu-uploader upload init.lua \r\n```\r\n\r\n\r\nUploading a number of files, but saving with a different file name. If you want an alternate \r\ndestination name, just add a colon \":\" and the new destination filename.\r\n\r\n```\r\nnodemcu-uploader upload init.lua:new_init.lua README.md:new_README.md\r\n```\r\n\r\nUploading with wildcard and compiling to .lc when uploaded.\r\n```\r\nnodemcu-uploader upload lib/*.lua --compile\r\n```\r\n\r\n\r\nUploading and verify successful uploading by downloading the file\r\nto RAM and comparing contents.\r\n\r\n```\r\nnodemcu-uploader.py upload init.lua --verify=raw\r\n```\r\n\r\nUploading and verify successful uploading by calculating the sha1\r\nchecksum on the esp and compare it to the checksum of the original file.\r\nThis requires the __crypto__ module in the firmware but it's more\r\nreliable than the _raw_ method.\r\n\r\n```\r\nnodemcu-uploader upload init.lua --verify=sha1\r\n```\r\n\r\n\r\n###Download\r\nFrom esp device to computer.\r\n\r\nDownloading a number of files.\r\nSupports multiple files. If you want an alternate destination name, just\r\nadd a colon \":\" and the new destination filename.\r\n```\r\nnodemcu-uploader download init.lua README.md nodemcu-uploader.py\r\n```\r\n\r\nDownloading a number of files, but saving with a different file name.\r\n\r\n```\r\nnodemcu-uploader download init.lua:new_init.lua README.md:new_README.md\r\n```\r\n\r\n### List files\r\nListing files, using serial port com1 on Windows\r\n```\r\nnodemcu-uploader --port com1 file list\r\n```\r\n\r\n### Do (execute) a file\r\n`nodemcu-uploader file do runme.lua`\r\n\r\nThis file has to exist on the device before, otherwise you will get an error.\r\n\r\n### Print a file\r\nThis will show the contents of an existing file.\r\n\r\n`nodemcu-uploader file print init.lua`\r\n\r\n\r\n### Listing heap memory size\r\n`nodemcu-uploader node heap`\r\n\r\n\r\n### Restarting the device\r\n`nodemcu-uploader node restart`\r\n\r\n\r\n### Format filesystem\r\n```\r\nnodemcu-uploader file format\r\n```\r\nNote that this can take a long time depending on size of flash on the device.\r\nEven if the tool timeout waiting for response from the device it might have \r\nworked. The tool was just not waiting long enough.\r\n\r\n### Remove specific files\r\n```\r\nnodemcu-uploader file remove foo.lua\r\n```\r\n\r\n## Misc\r\n### Setting default serial-port\r\nUsing the environment variable `SERIALPORT` you can avoid having to \r\ntype the `--port` option every time you use the tool.\r\n\r\nOn Windows, if your devices was connected to COM3 this could be done like this.\r\n```batch\r\nset SERIALPORT=com3\r\nREM on all subsequent commands the default port `com3`would be assumed\r\n\r\nnodemcu-uploader file list\r\n```\r\n"
  },
  {
    "path": "doc/bash_completion.d/nodemcu_uploader",
    "content": "_nodemcu_uploader_remote_files() {\n\tnodemcu-uploader file list 2>&1  | awk '/\\.lua\\s+[0-9]+$/ { print $1 }' | tr \"\\n\" ' '\n}\n\n_nodemcu_uploader() {\n\tlocal cur prev opts\n\n\tCOMPREPLY=()\n\tcur=\"${COMP_WORDS[COMP_CWORD]}\"\n\tprev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n\tprev_prev=\"${COMP_WORDS[COMP_CWORD-2]}\"\n\topts=\"--help --verbose --version --port --baud --start_baud --timeout --autobaud_time\"\n\tcmds=\"backup upload exec download file node terminal\"\n\tnode_options=\"heap restart\"\n\tfile_options=\"list do format remove print\"\n\n\tif  [[ ${cur} == -* ]] ; then\n\t\tCOMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n\telse\n\t\tcase $prev in\n\t\t\tnode )\t\tCOMPREPLY=( $(compgen -W \"${node_options}\" -- ${cur}) ) ;;\n\t\t\tfile )\t\tCOMPREPLY=( $(compgen -W \"${file_options}\" -- ${cur}) ) ;;\n\t\t\tupload )\tCOMPREPLY=( $(compgen -f -- ${cur}) ) ;;\n\t\t\tdownload )\tCOMPREPLY=( $(compgen -W \"$(_nodemcu_uploader_remote_files)\" -- ${cur}) ) ;;\n\t\t\t*)\n\t\t\t\tif [[ ${prev_prev} == file ]] ; then\n\t\t\t\t\tcase ${prev} in\n\t\t\t\t\t\tdo | print | remove ) COMPREPLY=( $(compgen -W \"$(_nodemcu_uploader_remote_files)\" -- ${cur}) ) ;;\n\t\t\t\t\tesac\n\t\t\t\telse\n\t\t\t\t\tCOMPREPLY=( $(compgen -W \"${cmds}\" -- ${cur}) )\n\t\t\t\tfi\n\t\tesac\n\tfi\n\n\treturn 0\n}\n\ncomplete -F _nodemcu_uploader nodemcu-uploader\n\n"
  },
  {
    "path": "nodemcu-uploader.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n# pylint: disable=C0103\n\"\"\"makes it easier to run nodemcu-uploader from command line\"\"\"\n\nfrom nodemcu_uploader import main\n\n\nif __name__ == '__main__':\n    main.main_func()\n"
  },
  {
    "path": "nodemcu_uploader/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n\n\"\"\"Library and util for uploading files to NodeMCU version 0.9.4 and later\"\"\"\n\nfrom .version import __version__  # noqa: F401\nfrom .uploader import Uploader  # noqa: F401\n"
  },
  {
    "path": "nodemcu_uploader/__main__.py",
    "content": "from .main import main_func\n\nif __name__ == '__main__':\n    main_func()\n"
  },
  {
    "path": "nodemcu_uploader/exceptions.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n# pylint: disable=C0111\n\n\"\"\"Various custom exceptions\"\"\"\n\n\nclass CommunicationTimeout(Exception):\n    def __init__(self, message, buf):\n        super(CommunicationTimeout, self).__init__(message)\n        self.buf = buf\n\n\nclass BadResponseException(Exception):\n    def __init__(self, message, expected, actual):\n        message = message + ' expected:`%s` != actual: `%s`' % (expected, actual)\n        super(BadResponseException, self).__init__(message)\n\n        self.expected = expected\n        self.actual = actual\n\n\nclass NoAckException(Exception):\n    pass\n\n\nclass DeviceNotFoundException(Exception):\n    pass\n\n\nclass VerificationError(Exception):\n    pass\n\n\nclass PathLengthException(Exception):\n    pass\n\n\nclass ValidationException(Exception):\n    def __init__(self, message, key, value):\n        message = \"Validation Exception. {key} was {message}. '{value}'\".format(message=message, key=key, value=value)\n        super(ValidationException, self).__init__(message)\n"
  },
  {
    "path": "nodemcu_uploader/luacode.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"This module contains all the LUA code that needs to be on the device\nto perform whats needed. They will be uploaded if they doesn't exist\"\"\"\n\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n# pylint: disable=C0301\n# flake8: noqa\n\n\nLUA_FUNCTIONS = ['recv_block', 'recv_name', 'recv', 'shafile', 'send_block', 'send_file', 'send']\n\nDOWNLOAD_FILE = \"file.open('{filename}') print(file.seek('end', 0)) file.seek('set', {bytes_read}) uart.write(0, file.read({chunk_size}))file.close()\"\n\nPRINT_FILE = \"file.open('{filename}') print('---{filename}---') print(file.read()) file.close() print('---')\"\n\nINFO_GROUP = \"for key,value in pairs(node.info('{group}')) do k=tostring(key) print(k .. string.rep(' ', 20 - #k), tostring(value)) end\"\n\nLIST_FILES = 'for key,value in pairs(file.list()) do print(key,value) end'\n# NUL = \\000, ACK = \\006\nRECV_LUA = \\\nr\"\"\"\nfunction recv()\n    local on,w,ack,nack=uart.on,uart.write,'\\6','\\21'\n    local fd\n    local function recv_block(d)\n        local t,l = d:byte(1,2)\n        if t ~= 1 then w(0, nack); fd:close(); return on('data') end\n        if l >= 0  then fd:write(d:sub(3, l+2)); end\n        if l == 0 then fd:close(); w(0, ack); return on('data') else w(0, ack) end\n    end\n    local function recv_name(d) d = d:gsub('%z.*', '') d:sub(1,-2) file.remove(d) fd=file.open(d, 'w') on('data', 130, recv_block, 0) w(0, ack) end\n    on('data', '\\0', recv_name, 0)\n    w(0, 'C')\n  end\nfunction shafile(f) print(crypto.toHex(crypto.fhash('sha1', f))) end\n\"\"\"  # noqa: E122\n\nSEND_LUA = \\\nr\"\"\"\nfunction send(f) uart.on('data', 1, function (data)\n  local on,w=uart.on,uart.write\n  local fd\n  local function send_block(d) l = string.len(d) w(0, '\\001' .. string.char(l) .. d .. string.rep('\\0', 128 - l)) return l end\n  local function send_file(f)\n    local s, p\n    fd=file.open(f) s=fd:seek('end', 0) p=0\n    on('data', 1, function(data)\n      if data == '\\006' and p<s then\n        fd:seek('set',p) p=p+send_block(fd:read(128))\n      else\n        send_block('') fd:close() on('data') print('interrupted')\n      end\n    end, 0)\n    w(0, f .. '\\000')\n  end\n  uart.on('data') if data == 'C' then send_file(f) else print('transfer interrupted') end end, 0)\nend\n\"\"\"\n\nUART_SETUP = 'uart.setup(0,{baud},8,0,1,1)'\n\nREMOVE_ALL_FILES = r\"\"\"\nfor key,value in pairs(file.list()) do file.remove(key) end\n\"\"\"\n"
  },
  {
    "path": "nodemcu_uploader/main.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n\n\"\"\"This module is the cli for the Uploader class\"\"\"\n\nfrom __future__ import print_function\n\nimport argparse\nimport logging\nimport os\nimport sys\nimport glob\nimport serial\nfrom .uploader import Uploader\nfrom .term import terminal\nfrom serial import VERSION as serialversion\nfrom .version import __version__\n\n\nlog = logging.getLogger(__name__)  # pylint: disable=C0103\n\n\ndef destination_from_source(sources, use_glob=True):\n    \"\"\"\n    Split each of the sources in the array on ':'\n    First part will be source, second will be destination.\n    Modifies the the original array to contain only sources\n    and returns an array of destinations.\n    \"\"\"\n    destinations = []\n    newsources = []\n    for i in range(0, len(sources)):\n        srcdst = sources[i].split(':')\n        if len(srcdst) == 2:\n            destinations.append(srcdst[1])\n            newsources.append(srcdst[0])  # proper list assignment\n        else:\n            if use_glob:\n                listing = glob.glob(srcdst[0])\n                for filename in listing:\n                    newsources.append(filename)\n                    # always use forward slash at destination\n                    destinations.append(filename.replace('\\\\', '/'))\n            else:\n                newsources.append(srcdst[0])\n                destinations.append(srcdst[0])\n\n    return [newsources, destinations]\n\n\ndef operation_upload(uploader, sources, verify, do_compile, do_file, do_restart):\n    \"\"\"The upload operation\"\"\"\n    if not isinstance(sources, list):\n        sources = [sources]\n    sources, destinations = destination_from_source(sources)\n    if len(destinations) == len(sources):\n        if uploader.prepare():\n            for filename, dst in zip(sources, destinations):\n                if do_compile:\n                    uploader.file_remove(os.path.splitext(dst)[0]+'.lc')\n                if not os.path.exists(filename) and not os.path.isfile(filename):\n                    raise Exception(\"File does not exist. {filename}\".format(filename=filename))\n                uploader.write_file(filename, dst, verify)\n                # init.lua is not allowed to be compiled\n                if do_compile and dst != 'init.lua':\n                    uploader.file_compile(dst)\n                    uploader.file_remove(dst)\n                    if do_file:\n                        uploader.file_do(os.path.splitext(dst)[0]+'.lc')\n                elif do_file:\n                    uploader.file_do(dst)\n        else:\n            raise Exception('Error preparing nodemcu for reception')\n    else:\n        raise Exception('You must specify a destination filename for each file you want to upload.')\n\n    if do_restart:\n        uploader.node_restart()\n    log.info('All done!')\n    return destinations\n\n\ndef operation_download(uploader, sources, *args, **kwargs):\n    \"\"\"The download operation\"\"\"\n    sources, destinations = destination_from_source(sources, False)\n    # print('sources', sources)\n    # print('destinations', destinations)\n    dest = kwargs.pop('dest', '')\n    if len(destinations) == len(sources):\n        if uploader.prepare():\n            for filename, dst in zip(sources, destinations):\n                dst = os.path.join(dest, dst)\n                uploader.read_file(filename, dst)\n    else:\n        raise Exception('You must specify a destination filename for each file you want to download.')\n    log.info('All done!')\n\n\ndef operation_list_files(uploader):\n    \"\"\"List file on target\"\"\"\n    files = uploader.file_list()\n    for f in files:\n        log.info(\"{file:30s} {size}\".format(file=f[0], size=f[1]))\n\n\ndef operation_file(uploader, cmd, filename=''):\n    \"\"\"File operations\"\"\"\n    if cmd == 'list':\n        operation_list_files(uploader)\n    if cmd == 'do':\n        for path in filename:\n            uploader.file_do(path)\n    elif cmd == 'format':\n        uploader.file_format()\n    elif cmd == 'remove':\n        for path in filename:\n            uploader.file_remove(path)\n    elif cmd == 'print':\n        for path in filename:\n            uploader.file_print(path)\n    elif cmd == 'remove_all':\n        uploader.file_remove_all()\n\n\ndef operation_port(args):\n    if args.cmd == 'list':\n        ports = serial.tools.list_ports.comports(include_links=False)\n        print('device', 'vid', 'pid')\n        for p in ports:\n            print(p.device, p.vid, p.pid)\n\n\ndef arg_auto_int(value):\n    \"\"\"parsing function for integer arguments\"\"\"\n    return int(value, 0)\n\n\ndef main_func():\n    \"\"\"Main function for cli\"\"\"\n    parser = argparse.ArgumentParser(\n        description='NodeMCU Lua file uploader',\n        prog='nodemcu-uploader'\n        )\n\n    parser.add_argument(\n        '--verbose',\n        help='verbose output',\n        action='store_true',\n        default=False)\n\n    parser.add_argument(\n        '--silent',\n        help='silent output. Errors and worse',\n        action='store_true',\n        default=False)\n\n    parser.add_argument(\n        '--version',\n        help='prints the version and exists',\n        action='version',\n        version='%(prog)s {version} (serial {serialversion}, python {pv})'.format(\n            version=__version__,\n            serialversion=serialversion,\n            pv=sys.version)\n    )\n\n    parser.add_argument(\n        '--port', '-p',\n        help='Serial port device',\n        default=Uploader.PORT)\n\n    parser.add_argument(\n        '--baud', '-b',\n        help='Serial port baudrate',\n        type=arg_auto_int,\n        default=Uploader.BAUD)\n\n    parser.add_argument(\n        '--start_baud', '-B',\n        help='Initial Serial port baudrate',\n        type=arg_auto_int,\n        default=Uploader.START_BAUD)\n\n    parser.add_argument(\n        '--timeout', '-t',\n        help='Timeout for operations',\n        type=arg_auto_int,\n        default=Uploader.TIMEOUT)\n\n    parser.add_argument(\n        '--autobaud_time', '-a',\n        help='Duration of the autobaud timer',\n        type=float,\n        default=Uploader.AUTOBAUD_TIME,\n    )\n\n    subparsers = parser.add_subparsers(\n        dest='operation',\n        help='Run nodemcu-uploader {command} -h for additional help')\n\n    backup_parser = subparsers.add_parser(\n        'backup',\n        help='Backup all the files on the nodemcu board')\n    backup_parser.add_argument('path', help='Folder where to store the backup')\n\n    upload_parser = subparsers.add_parser(\n        'upload',\n        help='Path to one or more files to be uploaded. Destination name will be the same as the file name.')\n\n    upload_parser.add_argument(\n        'filename',\n        nargs='+',\n        help='Lua file to upload. Use colon to give alternate destination.'\n        )\n\n    upload_parser.add_argument(\n        '--compile', '-c',\n        help='If file should be uploaded as compiled',\n        action='store_true',\n        default=False\n        )\n\n    upload_parser.add_argument(\n        '--verify', '-v',\n        help='To verify the uploaded data.',\n        action='store',\n        nargs='?',\n        choices=['none', 'raw', 'sha1'],\n        default='none'\n        )\n\n    upload_parser.add_argument(\n        '--dofile', '-e',\n        help='If file should be run after upload.',\n        action='store_true',\n        default=False\n        )\n\n    upload_parser.add_argument(\n        '--restart', '-r',\n        help='If esp should be restarted',\n        action='store_true',\n        default=False\n    )\n\n    exec_parser = subparsers.add_parser(\n        'exec',\n        help='Path to one or more files to be executed line by line.')\n\n    exec_parser.add_argument('filename', nargs='+', help='Lua file to execute.')\n\n    download_parser = subparsers.add_parser(\n        'download',\n        help='Path to one or more files to be downloaded. Destination name will be the same as the file name.')\n\n    download_parser.add_argument(\n        'filename',\n        nargs='+',\n        help='Lua file to download. Use colon to give alternate destination.')\n\n    file_parser = subparsers.add_parser(\n        'file',\n        help='File functions')\n\n    file_parser.add_argument(\n        'cmd',\n        choices=('list', 'do', 'format', 'remove', 'print', 'remove_all'),\n        help=\"\"\"list=list files, do=dofile given path, format=formate file area,\n            remove=remove given path, remove_all=delete all files\"\"\")\n\n    file_parser.add_argument('filename', nargs='*', help='path for cmd')\n\n    node_parse = subparsers.add_parser(\n        'node',\n        help='Node functions')\n\n    node_parse.add_argument(\n        'ncmd',\n        choices=('heap', 'restart', 'info'),\n        help=\"heap=print heap memory, restart=restart nodemcu, info=show node info\")\n\n    subparsers.add_parser(\n        'terminal',\n        help='Run pySerials miniterm'\n    )\n\n    port_parser = subparsers.add_parser(\n        'port',\n        help='serial port stuff'\n    )\n\n    port_parser.add_argument(\n        'cmd',\n        choices=('list',)\n    )\n\n    args = parser.parse_args()\n\n    default_level = logging.INFO\n    if args.silent:\n        default_level = logging.ERROR\n    if args.verbose:\n        default_level = logging.DEBUG\n\n    # formatter = logging.Formatter('%(message)s')\n\n    logging.basicConfig(level=default_level, format='%(message)s')\n\n    if args.operation == 'terminal':\n        # uploader can not claim the port\n        terminal(args.port, str(args.start_baud))\n        return\n    elif args.operation == 'port':\n        operation_port(args)\n        return\n\n    # let uploader user the default (short) timeout for establishing connection\n    uploader = Uploader(args.port, args.baud, start_baud=args.start_baud, autobaud_time=args.autobaud_time)\n\n    # and reset the timeout (if we have the uploader&timeout)\n    if args.timeout:\n        uploader.set_timeout(args.timeout)\n\n    if args.operation == 'upload':\n        operation_upload(uploader, args.filename, args.verify, args.compile, args.dofile,\n                         args.restart)\n\n    elif args.operation == 'download':\n        operation_download(uploader, args.filename)\n\n    elif args.operation == 'exec':\n        sources = args.filename\n        for path in sources:\n            uploader.exec_file(path)\n\n    elif args.operation == 'file':\n        operation_file(uploader, args.cmd, args.filename)\n\n    elif args.operation == 'node':\n        if args.ncmd == 'heap':\n            uploader.node_heap()\n        elif args.ncmd == 'restart':\n            uploader.node_restart()\n        elif args.ncmd == 'info':\n            uploader.node_info()\n\n    elif args.operation == 'backup':\n        uploader.backup(args.path)\n\n\n    # no uploader related commands after this point\n    uploader.close()\n"
  },
  {
    "path": "nodemcu_uploader/serialutils.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n\nfrom platform import system\nfrom os import environ\nfrom serial.tools import list_ports\n\n\ndef default_port(sysname=system(), detect=True):\n    \"\"\"This returns the default port used for different systems if SERIALPORT env variable is not set\"\"\"\n    system_default = {\n        'Windows': 'COM1',\n        'Darwin': '/dev/tty.SLAB_USBtoUART'\n    }.get(sysname, '/dev/ttyUSB0')\n    # if SERIALPORT is set then don't even waste time detecting ports\n    if 'SERIALPORT' not in environ and detect:\n        try:\n            ports = list_ports.comports(include_links=False)\n            if len(ports) == 1:\n                return ports[0].device\n            else:\n                # clever guessing, sort of\n                # vid/pid\n                # 4292/60000 adafruit huzzah\n                for p in ports:\n                    if p.vid == 4292 and p.pid == 60000:\n                        return p.device\n                # use last port as fallback\n                return ports[-1].device\n        except Exception:\n            pass\n\n    return environ.get('SERIALPORT', system_default)\n"
  },
  {
    "path": "nodemcu_uploader/term.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Piggyback on pyserial terminal\"\"\"\n\nfrom serial.tools import miniterm\nimport sys\n\nfrom .serialutils import default_port\n\n\ndef terminal(port=default_port(), baud='9600'):\n    \"\"\"Launch minterm from pyserial\"\"\"\n    testargs = ['nodemcu-uploader', port, baud]\n    # TODO: modifying argv is no good\n    sys.argv = testargs\n    # resuse miniterm on main function\n    miniterm.main()\n"
  },
  {
    "path": "nodemcu_uploader/uploader.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n\"\"\"Main functionality for nodemcu-uploader\"\"\"\n\n# Not sure about it, because UnicodeEncodeError throws anyway\n# from __future__ import unicode_literals\n\n\nimport time\nimport logging\nimport hashlib\nimport os\nimport errno\nimport serial\n\nfrom . import validate\nfrom .serialutils import default_port\nfrom .exceptions import CommunicationTimeout, DeviceNotFoundException, \\\n    BadResponseException, VerificationError, NoAckException\nfrom .utils import system, hexify, from_file, ENCODING\nfrom .luacode import RECV_LUA, SEND_LUA, LUA_FUNCTIONS, \\\n    LIST_FILES, UART_SETUP, PRINT_FILE, INFO_GROUP, REMOVE_ALL_FILES\n\n\nlog = logging.getLogger(__name__)  # pylint: disable=C0103\n\n__all__ = ['Uploader', 'default_port']\n\nSYSTEM = system()\n\n\nMINIMAL_TIMEOUT = 0.001\nBLOCK_START = b'\\x01'\nNUL = b'\\x00'\nACK = b'\\x06'\n\n\nclass Uploader(object):\n    \"\"\"Uploader is the class for communicating with the nodemcu and\n    that will allow various tasks like uploading files, formating the filesystem etc.\n    \"\"\"\n    BAUD = 115200\n    START_BAUD = 115200\n    TIMEOUT = 5\n    AUTOBAUD_TIME = 0.3\n    PORT = default_port()\n\n    def __init__(self, port=PORT, baud=BAUD, start_baud=START_BAUD, timeout=TIMEOUT, autobaud_time=AUTOBAUD_TIME):\n        self._timeout = Uploader.TIMEOUT\n        self.set_timeout(timeout)\n        log.info('opening port %s with %s baud', port, start_baud)\n        if port == 'loop://':\n            self._port = serial.serial_for_url(port, start_baud, timeout=timeout)\n        else:\n            self._port = serial.Serial(port, start_baud, timeout=timeout)\n\n        # black magic aka proxifying\n        # self._port = wrap(self._port)\n\n        self.start_baud = start_baud\n        self.baud = baud\n        self.autobaud_time = autobaud_time\n        # Keeps things working, if following connections are made:\n        # RTS = CH_PD (i.e reset)\n        # DTR = GPIO0\n        self._port.setRTS(False)\n        self._port.setDTR(False)\n\n        def __sync():\n            \"\"\"Get in sync with LUA (this assumes that NodeMCU gets reset by the previous two lines)\"\"\"\n            log.debug('getting in sync with LUA')\n            self.__clear_buffers()\n            try:\n                self.__writeln('UUUUUUUUUUUU')  # Send enough characters for auto-baud\n                self.__clear_buffers()\n                time.sleep(self.autobaud_time)  # Wait for autobaud timer to expire\n                self.__exchange(';')  # Get a defined state\n                self.__writeln('print(\"%sync%\");')\n                self.__expect('%sync%\\r\\n> ')\n            except CommunicationTimeout:\n                raise DeviceNotFoundException('Device not found or wrong port')\n\n        __sync()\n\n        if baud != start_baud:\n            self.__set_baudrate(baud)\n\n            # Get in sync again\n            __sync()\n\n        self.line_number = 0\n\n    def __set_baudrate(self, baud):\n        \"\"\"setting baudrate if supported\"\"\"\n        log.info('Changing communication to %s baud', baud)\n        self.__writeln(UART_SETUP.format(baud=baud))\n        # Wait for the string to be sent before switching baud\n        time.sleep(0.1)\n        try:\n            self._port.setBaudrate(baud)\n        except AttributeError:\n            # pySerial 2.7\n            self._port.baudrate = baud\n\n    def set_timeout(self, timeout):\n        \"\"\"Set the timeout for the communication with the device.\"\"\"\n        timeout = int(timeout)  # will raise on Error\n        self._timeout = timeout == 0 and 999999 or timeout\n\n    def __clear_buffers(self):\n        \"\"\"Clears the input and output buffers\"\"\"\n        try:\n            self._port.reset_input_buffer()\n            self._port.reset_output_buffer()\n        except AttributeError:\n            #  pySerial 2.\n            self._port.flushInput()\n            self._port.flushOutput()\n\n    def __expect(self, exp='> ', timeout=None):\n        \"\"\"will wait for exp to be returned from nodemcu or timeout.\n        Will use utils.ENCODING for encoding if not bytes.\n        \"\"\"\n        timeout_before = self._port.timeout\n        timeout = timeout or self._timeout\n        # do NOT set timeout on Windows\n        if SYSTEM != 'Windows':\n            # Checking for new data every 100us is fast enough\n            if self._port.timeout != MINIMAL_TIMEOUT:\n                self._port.timeout = MINIMAL_TIMEOUT\n\n        if not isinstance(exp, bytes):\n            exp = bytes(exp, ENCODING)\n\n        end = time.time() + timeout\n\n        # Finish as soon as either exp matches or we run out of time (work like dump, but faster on success)\n        data = bytes()\n        while not data.endswith(exp) and time.time() <= end:\n            data += self._port.read()\n            # msg = data.decode(ENCODING, 'ignore')\n\n        now = time.time()\n        log.debug('expect returned: `{0}`. wants: {1}'.format(data, exp))\n        if now > end:\n            raise CommunicationTimeout('Timeout waiting for data', data)\n\n        if not data.endswith(exp) and len(exp) > 0:\n            raise BadResponseException('Bad response.', exp, data)\n\n        if SYSTEM != 'Windows':\n            self._port.timeout = timeout_before\n\n        return str(data, ENCODING)\n\n    def __write(self, output, binary=False):\n        \"\"\"write data on the nodemcu port. Strings will be converted to bytes using utils.ENCODING.\n        If 'binary' is True the debug log will show the intended output as hex, otherwise as string\"\"\"\n        if not binary:\n            log.debug('write: %s', output)\n        else:\n            log.debug('write binary: %s', hexify(output))\n        if isinstance(output, str):\n            output = bytes(output, ENCODING)\n\n        self._port.write(output)\n        self._port.flush()\n\n    def __writeln(self, output):\n        \"\"\"write, with linefeed\"\"\"\n        self.__write(output + '\\n')\n\n    def __exchange(self, output, timeout=None):\n        \"\"\"Write output to the port and wait for response\n        Expects a str as input\"\"\"\n        if not isinstance(output, str):\n            raise TypeError(\"output should be a str\")\n        self.__writeln(output)\n        self._port.flush()\n        return self.__expect(timeout=timeout or self._timeout)\n\n    def close(self):\n        \"\"\"restores the nodemcu to default baudrate and then closes the port\"\"\"\n        try:\n            if self.baud != self.start_baud:\n                self.__set_baudrate(self.start_baud)\n            self._port.flush()\n            self.__clear_buffers()\n        except serial.serialutil.SerialException:\n            pass\n        log.debug('closing port')\n        self._port.close()\n\n    def prepare(self):\n        \"\"\"\n        This uploads the protocol functions nessecary to do binary\n        chunked transfer\n        \"\"\"\n        log.info('Preparing esp for transfer.')\n\n        for func in LUA_FUNCTIONS:\n            detected = self.__exchange('print({0})'.format(func))\n            if detected.find('function:') == -1:\n                break\n        else:\n            log.info('Preparation already done. Not adding functions again.')\n            return True\n        functions = RECV_LUA + '\\n' + SEND_LUA\n        data = functions.format(baud=self._port.baudrate)\n        # change any \\r\\n to just \\n and split on that\n        lines = data.replace('\\r', '').split('\\n')\n        # remove some unneccesary spaces to conserve some bytes\n        # TODO: a good minifier for lua\n        for line in lines:\n            line = line.strip().replace(', ', ',').replace(' = ', '=')\n\n            if len(line) == 0:\n                continue\n\n            resp = self.__exchange(line)\n            # do some basic test of the result\n            if ('unexpected' in resp) or ('stdin' in resp) or len(resp) > len(functions)+10:\n                log.error('error when preparing \"%s\"', resp)\n                return False\n        return True\n\n    def download_file(self, filename):\n        \"\"\"Download a file from device to RAM\n        Return 'bytes' of the full content\n        \"\"\"\n        validate.remotePath(filename)\n        res = self.__exchange('send(\"{filename}\")'.format(filename=filename))\n        if ('unexpected' in res) or ('stdin' in res):\n            log.error('Unexpected error downloading file: %s', res)\n            raise Exception('Unexpected error downloading file')\n\n        # tell device we are ready to receive\n        self.__write('C')\n        # we should get a NUL terminated filename to start with\n        sent_filename = self.__expect(NUL).strip()\n        log.info('receiveing ' + sent_filename)\n\n        # ACK to start download\n        self.__write(ACK, True)\n        buf = bytes()\n\n        data = bytes()\n        chunk, buf = self.__read_chunk(buf)\n        # read chunks until we get an empty which is the end\n        while len(chunk) > 0:\n            self.__write(ACK, True)\n            data = data + chunk\n            chunk, buf = self.__read_chunk(buf)\n        return data\n\n    def read_file(self, filename, destination=''):\n        \"\"\"Downloading data from remote device into local file using the transfer protocol.\n        \"\"\"\n        if not destination:\n            destination = filename\n        log.info('Transferring %s to %s', filename, destination)\n        data = self.download_file(filename)\n\n        # Just in case, the filename may contain folder, so create it if needed.\n        log.info(destination)\n        dirpath1 = os.path.dirname(destination)\n        if len(dirpath1) > 0 and not os.path.exists(dirpath1):\n            try:\n                os.makedirs(os.path.dirname(destination))\n            except OSError as e:  # Guard against race condition\n                if e.errno != errno.EEXIST:\n                    raise\n        with open(destination, 'wb') as fil:\n            try:\n                fil.write(data)\n            except Exception as e:\n                log.error(\"Unexpected error writing file\", e)\n                raise\n\n    def write_file(self, path, destination='', verify='none'):\n        \"\"\"Uploads a file to the remote device using the transfer protocol\"\"\"\n        filename = os.path.basename(path)\n        if not destination:\n            destination = filename\n\n        validate.remotePath(destination)\n        log.info('Transferring %s as %s', path, destination)\n        self.__writeln(\"recv()\")\n\n        res = self.__expect('C> ')\n        if not res.endswith('C> '):\n            log.error('Error waiting for esp \"%s\"', res)\n            raise CommunicationTimeout('Error waiting for device to start receiving', res)\n\n        log.debug('sending destination filename \"%s\"', destination)\n        self.__write(destination + '\\x00', True)\n        if not self.__got_ack():\n            log.error('did not ack destination filename')\n            raise NoAckException('Device did not ACK destination filename')\n\n        content = from_file(path)\n\n        log.debug('sending %d bytes in %s', len(content), filename)\n        pos = 0\n        chunk_size = 128\n        while pos < len(content):\n            rest = len(content) - pos\n            if rest > chunk_size:\n                rest = chunk_size\n\n            data = content[pos:pos+rest]\n            if not self.__write_chunk(data):\n                resp = self.__expect()\n                log.error('Bad chunk response \"%s\" %s', resp, hexify(resp))\n                raise BadResponseException('Bad chunk response', ACK, resp)\n\n            pos += chunk_size\n\n        log.debug('sending zero block')\n        # zero size block\n        self.__write_chunk()\n        if verify != 'none':\n            self.verify_file(path, destination, verify)\n\n    def verify_file(self, local, remote, verify='none'):\n        \"\"\"Tries to verify if local has same checksum as remote.\n            Valid options for verify is 'raw', 'sha1' or 'none'\n        \"\"\"\n        # get the local file contents\n        self.__writeln(';')\n        self.__expect('> ')\n        content = from_file(local)\n        log.info('Verifying using %s...' % verify)\n        if verify == 'raw':\n            data = self.download_file(remote)\n            if content != data:\n                log.error('Raw verification failed.')\n                raise VerificationError('Verification failed.')\n            else:\n                log.info('Verification successful. Contents are identical.')\n        elif verify == 'sha1':\n            # Calculate SHA1 on remote file. Extract just hash from result\n            data = self.__exchange('shafile(\"'+remote+'\")').splitlines()[1]\n            log.info('Remote SHA1: %s', data)\n\n            # Calculate hash of local data\n            filehashhex = hashlib.sha1(content).hexdigest()\n            log.info('Local SHA1: %s', filehashhex)\n            if data != filehashhex:\n                log.error('SHA1 verification failed.')\n                raise VerificationError('SHA1 Verification failed.')\n            else:\n                log.info('Verification successful. Checksums match')\n\n        elif verify != 'none':\n            raise Exception(verify + ' is not a valid verification method.')\n\n    def exec_file(self, path):\n        \"\"\"execute the lines in the local file 'path'\"\"\"\n        filename = os.path.basename(path)\n        log.info('Execute %s', filename)\n\n        content = from_file(path).replace('\\r', '').split('\\n')\n\n        res = '> '\n        for line in content:\n            line = line.rstrip('\\n')\n            retlines = (res + self.__exchange(line)).splitlines()\n            # Log all but the last line\n            res = retlines.pop()\n            for lin in retlines:\n                log.info(lin)\n        # last line\n        log.info(res)\n\n    def __got_ack(self):\n        \"\"\"Returns true if ACK is received\"\"\"\n        log.debug('waiting for ack')\n        res = self._port.read(1)\n        acked = res == ACK\n        log.debug('ack read %s, comparing with %s. %s', hexify(res), hexify(ACK), acked)\n        return acked\n\n    def write_lines(self, data):\n        \"\"\"write lines, one by one, separated by \\n to device\"\"\"\n        lines = data.replace('\\r', '').split('\\n')\n        for line in lines:\n            self.__exchange(line)\n\n    def __write_chunk(self, chunk=bytes()):\n        \"\"\"formats and sends a chunk of data to the device according to transfer protocol.\n        Return result of ack check\"\"\"\n        if not isinstance(chunk, bytes):\n            raise TypeError()\n        log.debug('writing %d bytes chunk', len(chunk))\n        data = BLOCK_START + bytes([len(chunk)]) + chunk\n        if len(chunk) < 128:\n            padding = 128 - len(chunk)\n            log.debug('pad with %d characters', padding)\n            data = data + (b'\\x00' * padding)\n\n        log.debug(\"packet size %d\", len(data))\n        self.__write(data)\n        self._port.flush()\n        return self.__got_ack()\n\n    def __read_chunk(self, buf):\n        \"\"\"Read a chunk of data\"\"\"\n        log.debug('reading chunk')\n        timeout_before = self._port.timeout\n        if SYSTEM != 'Windows':\n            # Checking for new data every 100us is fast enough\n            if self._port.timeout != MINIMAL_TIMEOUT:\n                self._port.timeout = MINIMAL_TIMEOUT\n\n        end = time.time() + timeout_before\n        if not isinstance(buf, bytes):\n            raise Exception('Buffer is not instance of \"bytes\"')\n        while len(buf) < 130 and time.time() <= end:\n            r = self._port.read()\n            if not isinstance(r, bytes):\n                raise Exception('r is not instance of \"bytes\" is {t}'.format(t=type(r).__name__))\n            buf = buf + r\n\n        if buf[0] != ord(BLOCK_START) or len(buf) < 130:\n            log.debug('buffer binary: %s ', hexify(buf))\n            raise Exception('Bad blocksize or start byte')\n        # else:\n        #     log.debug('buf binary: %s', hexify(buf))\n\n        if SYSTEM != 'Windows':\n            self._port.timeout = timeout_before\n\n        chunk_size = buf[1]\n        data = buf[2:chunk_size+2]\n        buf = buf[130:]\n        return (data, buf)\n\n    def file_list(self):\n        \"\"\"list files on the device\"\"\"\n        log.info('Listing files')\n        res = self.__exchange(LIST_FILES)\n        res = res.split('\\r\\n')\n        # skip first and last lines\n        res = res[1:-1]\n        files = []\n        for line in res:\n            files.append(line.split('\\t'))\n        return files\n\n    def file_do(self, filename):\n        \"\"\"Execute a file on the device using 'do'\"\"\"\n        log.info('Executing '+filename)\n        res = self.__exchange('dofile(\"'+filename+'\")')\n        log.info(res)\n        return res\n\n    def file_format(self):\n        \"\"\"Formats device filesystem\"\"\"\n        log.info('Formating, can take minutes depending on flash size...')\n        res = self.__exchange('file.format()', timeout=300)\n        if 'format done' not in res:\n            log.error(res)\n        else:\n            log.info(res)\n        return res\n\n    def file_print(self, filename):\n        \"\"\"Prints a file on the device to console\"\"\"\n        log.info('Printing ' + filename)\n        res = self.__exchange(PRINT_FILE.format(filename=filename))\n        log.info(res)\n        return res\n\n    def file_remove_all(self):\n        log.info('Removing all files!!!')\n        res = self.__exchange(REMOVE_ALL_FILES)\n        log.info(res)\n        return res\n\n    def node_heap(self):\n        \"\"\"Show device heap size\"\"\"\n        log.info('Heap')\n        res = self.__exchange('print(node.heap())')\n        log.info(res)\n        return int(res.split('\\r\\n')[1])\n\n    def node_restart(self):\n        \"\"\"Restarts device\"\"\"\n        log.info('Restart')\n        res = self.__exchange('node.restart()')\n        log.info(res)\n        return res\n\n    def node_info(self):\n        \"\"\"Node info\"\"\"\n        log.info('Node info')\n        res = self.node_info_group('hw')\n        res += self.node_info_group('sw_version')\n        res += self.node_info_group('build_config')\n        return res\n\n    def node_info_group(self, group):\n        log.info('Node info %s', group)\n        res = self.__exchange(INFO_GROUP.format(group=group))\n        log.info(res)\n        return res\n\n    def file_compile(self, path):\n        \"\"\"Compiles a file specified by path on the device\"\"\"\n        log.info('Compile '+path)\n        cmd = 'node.compile(\"%s\")' % path\n        res = self.__exchange(cmd)\n        log.info(res)\n        return res\n\n    def file_remove(self, path):\n        \"\"\"Removes a file on the device\"\"\"\n        log.info('Remove '+path)\n        cmd = 'file.remove(\"%s\")' % path\n        res = self.__exchange(cmd)\n        log.info(res)\n        return res\n\n    def backup(self, path):\n        \"\"\"Backup all files from the device\"\"\"\n        log.info('Backing up in '+path)\n        # List file to backup\n        files = self.file_list()\n        # then download each of then\n        self.prepare()\n        for f in files:\n            self.read_file(f[0], os.path.join(path, f[0]))\n"
  },
  {
    "path": "nodemcu_uploader/utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n\"\"\"Various utility functions\"\"\"\n\nfrom platform import system\n\n# from wrapt import ObjectProxy\nfrom sys import version_info\n\n__all__ = ['system', 'hexify', 'from_file', 'PY2', 'ENCODING']\n\nPY2 = version_info.major == 2\n\nif PY2:\n    raise Exception(\"Python 2 is no longer supported\")\n\nENCODING = 'latin1'\n\n\ndef to_hex(x):\n    if isinstance(x, int):\n        return hex(x)\n    return hex(ord(x))\n\n\ndef hexify(byte_arr):\n    if isinstance(byte_arr, int):\n        return to_hex(byte_arr)[2:]\n    else:\n        return ':'.join((to_hex(x)[2:] for x in byte_arr))\n\n\ndef from_file(path):\n    \"\"\"Returns content of file as 'bytes'.\n    \"\"\"\n    with open(path, 'rb') as f:\n        content = f.read()\n    return content\n"
  },
  {
    "path": "nodemcu_uploader/validate.py",
    "content": "from .exceptions import ValidationException\n\nMAX_FS_NAME_LEN = 31\n\n\ndef remotePath(path):\n    \"\"\"Do various checks on the remote file name like max length.\n    Raises exception if not valid\n    \"\"\"\n    if len(path) > MAX_FS_NAME_LEN:\n        raise ValidationException('To long. >{0}'.format(MAX_FS_NAME_LEN), 'path', path)\n    if len(path) < 1:\n        raise ValidationException('To short', 'path', path)\n"
  },
  {
    "path": "nodemcu_uploader/version.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n\"\"\"just keeper of current version\"\"\"\n\n# TODO: remember to update tests when version changes\n__version__ = '1.0.0'\n"
  },
  {
    "path": "pylintrc",
    "content": "[MASTER]\npersistent=yes\n\n[FORMAT]\n# Maximum number of characters on a single line.\nmax-line-length=120\n\n[DESIGN]\nmax-args=6\nmax-statements=70\n\n[MESSAGES CONTROL]\ndisable=I0011\n\n[REPORTS]\nmsg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}\ninclude-ids=yes"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\ndescription_file=README.md\nlicence=LICENSE\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2020 Peter Magnusson <me@kmpm.se>\n\"\"\"Setup for nodemcu-uploader\"\"\"\n\nfrom setuptools import setup\n\nexec(open('nodemcu_uploader/version.py').read())  # pylint: disable=W0122\n\nwith open(\"README.md\", \"r\") as fh:\n    long_description = fh.read()\n\n\nsetup(name='nodemcu-uploader',\n    version=__version__,  # noqa: F821\n    python_requires='>=3.5',\n    install_requires=[\n        'pyserial>=3.4'\n    ],\n    packages=['nodemcu_uploader'],\n    # package_dir={'nodemcu_uploader': 'lib'},\n    url='https://github.com/kmpm/nodemcu-uploader',\n    author='kmpm',\n    author_email='me@kmpm.se',\n    description='tool for uploading files to the filesystem of an ESP8266 running NodeMCU.',\n    long_description=long_description,\n    long_description_content_type=\"text/markdown\",\n    keywords=['esp8266', 'upload', 'nodemcu'],\n    classifiers=[\n        'Development Status :: 4 - Beta',\n        'Intended Audience :: Developers',\n        'Programming Language :: Python :: 3'\n    ],\n    license='MIT',\n    test_suite=\"tests.get_tests\",\n    entry_points={\n        'console_scripts': [\n            'nodemcu-uploader=nodemcu_uploader.main:main_func'\n        ]\n    },\n    zip_safe=False,\n)\n"
  },
  {
    "path": "test_requirements.txt",
    "content": "pyserial==3.4\ncoverage==4.0.3\nflake8==3.7.9\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n\"\"\"Add tests to include here\"\"\"\n\nimport unittest\nimport logging\n\ndef get_tests():\n    \"\"\"returns the tests to run\"\"\"\n    return full_suite()\n\ndef full_suite():\n    \"\"\"creates a full suite of tests\"\"\"\n    logging.basicConfig(filename='test.log', level=logging.INFO,\n        format='%(asctime)s %(levelname)s %(module)s.%(funcName)s %(message)s')\n\n    from .misc import MiscTestCase\n    from . import uploader\n    # from .serializer import ResourceTestCase as SerializerTestCase\n    # from .utils import UtilsTestCase\n\n    miscsuite = unittest.TestLoader().loadTestsFromTestCase(MiscTestCase)\n    uploadersuite = unittest.TestLoader().loadTestsFromModule(uploader)\n    return unittest.TestSuite([miscsuite, uploadersuite])\n"
  },
  {
    "path": "tests/fixtures/big_file.txt",
    "content": "xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz"
  },
  {
    "path": "tests/fixtures/led_blink.lua",
    "content": "lighton=0\ntmr.alarm(0,1000,1,function()\nif lighton==0 then \n    lighton=1 \n    led(512,512,512) \n    -- 512/1024, 50% duty cycle\nelse \n    lighton=0 \n    led(0,0,0) \nend \nend)\n"
  },
  {
    "path": "tests/fixtures/medium_file.txt",
    "content": "xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz\r\n1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
  },
  {
    "path": "tests/fixtures/riazzerawifi.lua",
    "content": "--riazzerawifi.lua\n-- Starts the portal to choose the wi-fi router to which we have\n-- to associate\nwifi.sta.disconnect()\nwifi.setmode(wifi.STATIONAP)\n--ESP SSID generated wiht its chipid\nwifi.ap.config({ssid=\"Mynode-\"..node.chipid()\n, auth=wifi.OPEN})\nenduser_setup.manual(true)\nenduser_setup.start(\n  function()\n    print(\"Connected to wifi as:\" .. wifi.sta.getip())\n  end,\n  function(err, str)\n    print(\"enduser_setup: Err #\" .. err .. \": \" .. str)\n  end\n);\n"
  },
  {
    "path": "tests/fixtures/small_file.txt",
    "content": "xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz"
  },
  {
    "path": "tests/fixtures/testuploadfail.txt",
    "content": "    if (anything == false and test > 10) then print (\"This line should be fine\") end\r\n\r\n-- Doesn't matter what is here, just needs to be enough for another block after the > sign\r\n-- Lorem Ipsum dolor sit amet consecutor adlipsing. Lorem Ipsum dolor sit amet consecutor adlipsing.\r\n-- Lorem Ipsum dolor sit amet consecutor adlipsing.Lorem Ipsum dolor sit amet consecutor adlipsing.\r\n-- Lorem Ipsum dolor sit amet consecutor adlipsing.Lorem Ipsum dolor sit amet consecutor adlipsing.\r\n"
  },
  {
    "path": "tests/fixtures/webserver.lua",
    "content": "-- webserver.lua\n--webserver sample from the nodemcu github\nif srv~=nil then\n    srv:close()\n   end\n   \n   gpio.mode(1, gpio.OUTPUT)\n   srv=net.createServer(net.TCP)\n   srv:listen(80,function(conn)\n       conn:on(\"receive\", function(client,request)\n           local buf = \"\"\n           local _, _, method, path, vars = string.find(request, \"([A-Z]+) (.+)?(.+) HTTP\")\n           if(method == nil)then\n               _, _, method, path = string.find(request, \"([A-Z]+) (.+) HTTP\")\n           end\n           local _GET = {}\n           if (vars ~= nil)then\n               for k, v in string.gmatch(vars, \"(%w+)=(%w+)&*\") do\n                   _GET[k] = v\n               end\n           end\n           buf = buf..\"<h1> Hello, NodeMcu.</h1><form src=\\\"/\\\">Turn PIN1 <select name=\\\"pin\\\" onchange=\\\"form.submit()\\\">\"\n           local _on,_off = \"\",\"\"\n           if(_GET.pin == \"ON\")then\n                 _on = \" selected=true\"\n                 gpio.write(1, gpio.HIGH)\n           elseif(_GET.pin == \"OFF\")then\n                 _off = \" selected=\\\"true\\\"\"\n                 gpio.write(1, gpio.LOW)\n           end\n           buf = buf..\"<option\".._on..\">ON</opton><option\".._off..\">OFF</option></select></form>\"\n           client:send(buf)\n       end)\n       conn:on(\"sent\", function (c) c:close() end)\n   end)\n"
  },
  {
    "path": "tests/misc.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\nimport unittest\nfrom nodemcu_uploader.serialutils import default_port\nfrom nodemcu_uploader import __version__\nimport os\n\nfrom nodemcu_uploader import validate, exceptions\n\n\nclass MiscTestCase(unittest.TestCase):\n\n    def test_version(self):\n        self.assertEqual(__version__, '1.0.0')\n\n    def test_default_port(self):\n        if os.environ.get('SERIALPORT', 'none') != 'none':\n            # SERIALPORT should override any system defaults\n            self.assertEqual(default_port(), os.environ['SERIALPORT'])\n        else:\n            # Test as if it were given system\n            self.assertEqual(default_port('Linux', False), '/dev/ttyUSB0')\n            self.assertEqual(default_port('Windows', False), 'COM1')\n            self.assertEqual(default_port('Darwin', False), '/dev/tty.SLAB_USBtoUART')\n\n    def test_remote_path_validation(self):\n        validate.remotePath(\"test/something/maximum/len.jpeg\")\n        validate.remotePath(\"a\")\n\n        def v(p):\n            validate.remotePath(p)\n\n        self.assertRaises(exceptions.ValidationException, (lambda: v(\"test/something/maximum/leng.jpeg\")))\n        self.assertRaises(exceptions.ValidationException, (lambda: v(\"\")))\n"
  },
  {
    "path": "tests/torture.py",
    "content": "# -*- coding: utf-8 -*-\nimport unittest\nimport logging\nimport time\nimport os\nfrom nodemcu_uploader import Uploader\nfrom nodemcu_uploader.main import operation_download, operation_upload\nimport shutil\n\nlog = logging.getLogger(__name__)\nlogging.basicConfig(\n    filename='test.log',\n    level=logging.INFO,\n    format='%(asctime)s %(levelname)s %(module)s.%(funcName)s %(message)s')\n\nLOOPPORT = 'loop://'\n\n# on which port should the tests be performed\nSERIALPORT = os.environ.get('SERIALPORT', LOOPPORT)\n\n\ndef is_real():\n    if SERIALPORT.strip() == '':\n        return False\n    return str(SERIALPORT) != str(LOOPPORT)\n\n\n@unittest.skipUnless(is_real(), 'Needs a configured SERIALPORT')\nclass TestTorture(unittest.TestCase):\n    uploader = None\n\n    def setUp(self):\n        log.info(\"setUp\")\n        self.uploader = Uploader(SERIALPORT)\n\n    def tearDown(self):\n        log.info(\"tearDown\")\n        if is_real():\n            self.uploader.node_restart()\n        self.uploader.close()\n        time.sleep(1)\n\n    def task_upload_verify_compile(self):\n        \"\"\"Upload lua code, verify and compile\"\"\"\n        log.info('upload-verify-compile')\n        self.assertTrue(self.uploader.prepare())\n        dests = operation_upload(self.uploader, \"tests/fixtures/*.lua\", 'sha1', True, False, False)\n        return len(dests)\n\n    def task_upload_verify(self):\n        \"\"\"Upload some text files and verify\"\"\"\n        log.info('upload-verify')\n        dests = operation_upload(self.uploader, \"tests/fixtures/*_file.txt\", 'sha1', False, False, False)\n        return len(dests)\n\n    def task_check_remote_files(self, wanted):\n        \"\"\"Check that the wanted number of files exists on the device\"\"\"\n        lst = self.uploader.file_list()\n        self.assertIsInstance(lst, type([]))\n        self.assertEqual(len(lst), wanted)\n        return lst\n\n    def task_remove_all_files(self):\n        \"\"\"Remove all files on device\"\"\"\n        log.info('remove all files')\n        self.uploader.file_remove_all()\n\n    def task_download_all_files(self, files):\n        \"\"\"Downloads all files on device and do a sha1 checksum\"\"\"\n        log.info('download all files and verify. %s', files)\n        dest = os.path.join('.', 'tmp')\n        operation_download(self.uploader, files, dest=dest)\n        for f in files:\n            local = os.path.join(dest, f)\n            self.assertTrue(os.path.isfile(local))\n            self.uploader.verify_file(local, f, 'sha1')\n\n    def task_remove_tmp(self):\n        \"\"\"Removes local tmp folder\"\"\"\n        dest = os.path.join('.', 'tmp')\n        if os.path.isdir(dest):\n            shutil.rmtree(dest)\n\n    def test_for_long_time(self):\n        \"\"\"Run a sequence of steps a number of times\"\"\"\n        testcount = 10\n        for x in range(testcount):\n            print('test sequence {0}/{1}'.format(x+1, testcount))\n            log.info('--- test sequence {0}/{1} ---'.format(x+1, testcount))\n            self.task_remove_tmp()\n            self.task_remove_all_files()\n            self.task_check_remote_files(0)\n            time.sleep(0.5)\n            count = self.task_upload_verify_compile()\n            self.assertEqual(count, 3)\n            count += self.task_upload_verify()\n            self.assertEqual(count, 6)\n            files = self.task_check_remote_files(count)\n            self.task_download_all_files(list(map(lambda x: x[0], files)))\n"
  },
  {
    "path": "tests/uploader.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright (C) 2015-2019 Peter Magnusson <peter@kmpm.se>\n# pylint: disable=C0111,R0904\nimport unittest\nimport time\nimport os\nfrom nodemcu_uploader import Uploader\n# from serial import VERSION as serialversion\n# from distutils.version import LooseVersion\n\nLOOPPORT = 'loop://'\n\n# on which port should the tests be performed\nSERIALPORT = os.environ.get('SERIALPORT', LOOPPORT)\n\n\ndef is_real():\n    if SERIALPORT.strip() == '':\n        return False\n    return str(SERIALPORT) != str(LOOPPORT)\n\n# @unittest.skipUnless(LooseVersion(serialversion) >= LooseVersion('3.0.0') , 'Needs pySerial >= 3.0.0')\n# class UploaderFakeTestCase(unittest.TestCase):\n#     def test_init(self):\n#         uploader = Uploader(SERIALPORT)\n#         uploader.close()\n\n\n@unittest.skipUnless(is_real(), 'Needs a configured SERIALPORT')\nclass UploaderTestCase(unittest.TestCase):\n    uploader = None\n\n    def setUp(self):\n        self.uploader = Uploader(SERIALPORT)\n\n    def tearDown(self):\n        if is_real():\n            self.uploader.node_restart()\n        self.uploader.close()\n        time.sleep(1)\n\n    def test_upload_and_verify_raw(self):\n        self.uploader.prepare()\n        self.uploader.write_file('tests/fixtures/big_file.txt', verify='raw')\n\n    def test_upload_and_verify_sha1(self):\n        self.uploader.prepare()\n        self.uploader.write_file('tests/fixtures/big_file.txt', verify='sha1')\n\n    def test_upload_strange_file(self):\n        self.uploader.prepare()\n        self.uploader.write_file('tests/fixtures/testuploadfail.txt', verify='raw')\n\n    def test_file_list(self):\n        lst = self.uploader.file_list()\n        self.assertIsInstance(lst, type([]))\n        self.assertGreaterEqual(len(lst), 1)\n        self.assertLess(len(lst), 50)\n\n    def test_node_heap(self):\n        size = self.uploader.node_heap()\n        self.assertGreater(size, 20000)\n        self.assertLess(size, 60000)\n\n    def test_node_info(self):\n        result = self.uploader.node_info()\n        self.assertNotIn(\"deprecated\", result)\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py36, py37, py38\n\n[testenv]\ndeps = -rtest_requirements.txt\ncommands = python -m unittest -v tests.get_tests\nsetenv =\n  SERIALPORT=\n\n[flake8]\ninclude =\n    nodemcu_uploader,\n    tests\n# ignore = E501\nmax-line-length = 120\n"
  }
]