├── .gitignore ├── LICENSE ├── MANIFEST.in ├── PSOC └── CANbus_Bootloader.c ├── README.md ├── cyflash ├── __init__.py ├── __main__.py ├── bootload.py ├── cyacd.py ├── cyacd_test.py └── protocol.py ├── logging.conf.example └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Pycharm Stuff 53 | .idea/ 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Arachnid Labs Ltd 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include logging.conf.example 3 | recursive-include cyflash *.py 4 | recursive-include PSOC *.c 5 | -------------------------------------------------------------------------------- /PSOC/CANbus_Bootloader.c: -------------------------------------------------------------------------------- 1 | /** \file 2 | * 3 | * This file implements the Cypress bootloader transport interface 4 | * on raw standard (11bit IDs) CANbus frames. 5 | * The CANbus component should be named "CAN", no interrupts, all 6 | * mboxes configured as basic. 7 | * The Bootloader component needs to be configured on "custom interface" 8 | * communication component, since I'm too lazy to implement the remaining 9 | * bells & whistles. 10 | * If you want to update specific devices on the bus, define 11 | * CHECK_DEVICE_CANBUS_ID and place the relevant ID in the external 12 | * CANbus_ID variable. In this case you can also define a "broadcast" id with 13 | * CANBUS_BROADCAST_ID. 14 | * If neither are defined all incoming frames are used. 15 | * By default the frame is just read from CANbus and passed up. 16 | * Optionally you can define ECHO_CANBUS_FRAMES in order to echo back the read 17 | * frame on the bus using own device ID. Useful if you are updating a single 18 | * device at a time and want to keep the bootloader 19 | * host in sync. 20 | * 21 | * \author Giuseppe Corbelli 22 | * \author Weightpack SRL - http://www.weightpack.com 23 | * \copyright GNU Public License V3 24 | * 25 | * SVN ID: $Id$ 26 | */ 27 | 28 | #include 29 | 30 | #include "CAN.h" 31 | 32 | /** How much do we wait between mailboxes checks */ 33 | #define WAIT_STEP_MS 1u 34 | 35 | /** Check if an RX mailbox is full */ 36 | #if (CY_PSOC3 || CY_PSOC5) 37 | # define CAN_RX_MAILBOX_IS_FULL(i) ((CAN_RX[i].rxcmd.byte[0u] & CAN_RX_ACK_MSG) != 0) 38 | #else /* CY_PSOC4 */ 39 | # define CAN_RX_MAILBOX_IS_FULL(i) ((CAN_RX_CMD_REG(i) & CAN_RX_ACK_MSG) != 0) 40 | #endif /* CY_PSOC3 || CY_PSOC5 */ 41 | 42 | /** Mark an RX mailbox as "free", that is mark the message as processed */ 43 | #if (CY_PSOC3 || CY_PSOC5) 44 | # define CAN_RX_MAILBOX_FREE(i) (CAN_RX[i].rxcmd.byte[0u] |= CAN_RX_ACK_MSG) 45 | #else /* CY_PSOC4 */ 46 | # define CAN_RX_MAILBOX_FREE(i) (CAN_RX_CMD_REG(i) |= CAN_RX_ACK_MSG) 47 | #endif /* CY_PSOC3 || CY_PSOC5 */ 48 | 49 | /** Check if a TX mailbox is full */ 50 | #if (CY_PSOC3 || CY_PSOC5) 51 | // Both should be the same 52 | // #define CAN_TX_MAILBOX_IS_FULL(i) (CAN_BUF_SR_REG.byte[2] & (uint8)(1u << i)) 53 | # define CAN_TX_MAILBOX_IS_FULL(i) (CAN_TX[i].txcmd.byte[0] & CAN_TX_REQUEST_PENDING) 54 | #else /* CY_PSOC4 */ 55 | # define CAN_TX_MAILBOX_IS_FULL(i) ((CAN_TX_CMD_REG(i) & CAN_TX_REQUEST_PENDING) != 0) 56 | #endif /* CY_PSOC3 || CY_PSOC5 */ 57 | 58 | // -------------------------------------------------------------------------- 59 | 60 | // Pointer to the last RX mailbox examined. Must apply a FIFO order. 61 | static uint8 mailbox = 0; 62 | 63 | extern uint16 CANbus_ID; 64 | 65 | // -------------------------------------------------------------------------- 66 | 67 | void CyBtldrCommStart(void) 68 | { 69 | CAN_Start(); 70 | } 71 | 72 | void CyBtldrCommStop(void) 73 | { 74 | CAN_Stop(); 75 | } 76 | 77 | void CyBtldrCommReset(void) 78 | { 79 | uint8 i; 80 | 81 | // Abort pending messages 82 | for (i = 0u; i < CAN_NUMBER_OF_TX_MAILBOXES; i++) { 83 | CAN_TX_ABORT_MESSAGE(i); 84 | CAN_RX_RTR_ABORT_MESSAGE(i); 85 | } 86 | CAN_Stop(); 87 | CAN_Start(); 88 | } 89 | 90 | /** Write a packet to basic TX mailbox 0 */ 91 | cystatus CyBtldrCommWrite(uint8* buffer, uint16 size, uint16* count, uint8 timeout) 92 | { 93 | CAN_TX_MSG msg; 94 | uint32 regTemp; 95 | int16 timeout_ms = 10 * timeout; 96 | uint8 i, j; 97 | uint16 pointer = 0; 98 | uint8 chunk = 0; 99 | cystatus result = CYRET_TIMEOUT; 100 | 101 | if (size == 0) 102 | return CYRET_TIMEOUT; 103 | 104 | // For sake of simplicity we're using just mailbox 0 105 | // Besides, we prefer to go slow and safe and make sure 106 | // no outgoing messages are overlapping (in terms of priorities) 107 | // Make sure it's a basic mailbox 108 | CYASSERT((CAN_TX_MAILBOX_TYPE & 0x01) == 0u); 109 | 110 | msg.id = CANbus_ID; 111 | msg.ide = CAN_STANDARD_MESSAGE; 112 | msg.rtr = CAN_STANDARD_MESSAGE; 113 | msg.irq = CAN_TRANSMIT_INT_DISABLE; 114 | 115 | // Make sure there's no TX pending in the first mbox 116 | do { 117 | if (!CAN_TX_MAILBOX_IS_FULL(0)) 118 | break; 119 | if (timeout) { 120 | CyDelay(WAIT_STEP_MS); 121 | timeout_ms -= WAIT_STEP_MS; 122 | } 123 | } while (timeout_ms >= 0); 124 | 125 | if (timeout_ms < 0) { 126 | return CYRET_TIMEOUT; 127 | } 128 | 129 | // Ok, mailbox is free 130 | while ((pointer < size) && (timeout_ms >= 0)) { 131 | chunk = ((size - pointer) > CAN_TX_DLC_MAX_VALUE) ? CAN_TX_DLC_MAX_VALUE : (size - pointer); 132 | msg.dlc = chunk; 133 | 134 | regTemp = 0u; 135 | 136 | /* Set message parameters */ 137 | CAN_SET_TX_ID_STANDARD_MSG(0, msg.id); 138 | if (msg.dlc < CAN_TX_DLC_MAX_VALUE) { 139 | regTemp |= ((uint32)msg.dlc) << CAN_TWO_BYTE_OFFSET; 140 | } else { 141 | regTemp |= CAN_TX_DLC_UPPER_VALUE; 142 | } 143 | for (j = 0u; (j < msg.dlc) && (j < CAN_TX_DLC_MAX_VALUE); j++) { 144 | CAN_TX_DATA_BYTE(0, j) = buffer[j+pointer]; 145 | } 146 | pointer += chunk; 147 | /* Reuse variable to mark the current error count */ 148 | j = CAN_GetTXErrorCount(); 149 | 150 | /* Disable isr */ 151 | CyIntDisable(CAN_ISR_NUMBER); 152 | /* WPN[23] and WPN[3] set to 1 for write to CAN Control reg */ 153 | CY_SET_REG32(CAN_TX_CMD_PTR(0), (regTemp | CAN_TX_WPN_SET)); 154 | CY_SET_REG32(CAN_TX_CMD_PTR(0), CAN_SEND_MESSAGE); 155 | /* Enable isr */ 156 | CyIntEnable(CAN_ISR_NUMBER); 157 | 158 | // Check that the mailbox is free (that is, frame sent) 159 | do { 160 | i = CAN_GetTXErrorCount(); 161 | if (j != i) { 162 | // TX failed, error count increased 163 | CAN_TxCancel(0); 164 | return CYRET_TIMEOUT; 165 | } else { 166 | result = CYRET_SUCCESS; 167 | } 168 | if (timeout) { 169 | CyDelay(WAIT_STEP_MS); 170 | timeout_ms -= WAIT_STEP_MS; 171 | } 172 | } while (CAN_TX_MAILBOX_IS_FULL(0) && (CAN_GetErrorState() == 0) && (timeout_ms >= 0)); 173 | } 174 | 175 | if (timeout_ms < 0) { 176 | return CYRET_TIMEOUT; 177 | } 178 | 179 | // Useless as of bootloader v1.5 180 | *count = size; 181 | 182 | result |= CAN_GetErrorState(); 183 | return (result == 0) ? CYRET_SUCCESS : CYRET_TIMEOUT; 184 | } 185 | 186 | /** Read a packet from a basic RX mailbox (FIFO) and optionally echo it back before returning */ 187 | cystatus CyBtldrCommRead(uint8* buffer, uint16 size, uint16* count, uint8 timeout) 188 | { 189 | int16 timeout_ms = 10 * timeout; 190 | uint16 frame_id; 191 | uint16 copied = 0; 192 | uint16 pointer = 0; 193 | uint8 full_mailboxes = 0; 194 | uint8 dlc; 195 | uint8 bswap_dest[] = {3u, 2u, 1u, 0u, 7u, 6u, 5u, 4u}; 196 | #if (CY_PSOC3) 197 | bit got_sop; 198 | bit got_eop; 199 | #else 200 | uint8 got_sop; 201 | uint8 got_eop; 202 | #endif 203 | 204 | *count = 0; 205 | if (size == 0) 206 | return CYRET_SUCCESS; 207 | 208 | memset(buffer, 0, size); 209 | 210 | // Make sure we have room in the buffer to accomodate a full CAN frame 211 | // Buffer should be 300 bytes, see Bootloader_SIZEOF_COMMAND_BUFFER 212 | CYASSERT(size >= CAN_TX_DLC_MAX_VALUE); 213 | 214 | do { 215 | // Restart from the last-used mailbox, see static definition above 216 | if (mailbox >= CAN_NUMBER_OF_RX_MAILBOXES) { 217 | mailbox = 0; 218 | full_mailboxes = 0; 219 | } 220 | for (; mailbox < CAN_NUMBER_OF_RX_MAILBOXES; mailbox++) { 221 | // Reuse variable 222 | copied = CAN_GetErrorState(); 223 | switch (copied) { 224 | case (0x0000): // Error active 225 | case (0x0001): // Error passive 226 | break; 227 | default: // Bus off 228 | return CYRET_INVALID_STATE; 229 | } 230 | 231 | // Check message available 232 | if (!CAN_RX_MAILBOX_IS_FULL(mailbox)) { 233 | // No message, check next mailbox 234 | continue; 235 | } 236 | 237 | #ifdef CHECK_DEVICE_CANBUS_ID 238 | frame_id = (CY_GET_REG32((reg32 *) (&CAN_RX->rxid)) >> CAN_SET_TX_ID_STANDARD_MSG_SHIFT); 239 | if ((frame_id != CANbus_ID) 240 | # ifdef CANBUS_BROADCAST_ID 241 | && (frame_id != CANBUS_BROADCAST_ID) 242 | # endif // CANBUS_BROADCAST_ID 243 | ) 244 | { 245 | CAN_RX_MAILBOX_FREE(mailbox); 246 | continue; // Message for another recipient, next mbox 247 | } 248 | #endif 249 | 250 | full_mailboxes++; 251 | 252 | dlc = CAN_RX[mailbox].rxcmd.byte[2u] & 0x0F; 253 | copied = dlc; 254 | // Apparently a data payload sent from IXXAT Minimon/Codesys as 255 | // 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 256 | // gets interpreted here as 257 | // 0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05 258 | // Seems the usual big/little endian issue 259 | 260 | while (copied) { 261 | copied--; 262 | buffer[*count + copied] = CAN_RX[mailbox].rxdata.byte[bswap_dest[copied]]; 263 | } 264 | CAN_RX_MAILBOX_FREE(mailbox); 265 | 266 | #ifdef ECHO_CANBUS_FRAMES 267 | // Send back what we have just received 268 | CyBtldrCommWrite(buffer+(*count), dlc, &copied, 0); 269 | #endif // ECHO_CANBUS_FRAMES 270 | 271 | *count += dlc; 272 | 273 | // Check if the high level packet is completed, so we can stop 274 | // the reading loop without waiting for timeout 275 | 276 | // Reuse variable, data length field in packet 277 | copied = *(uint16*)(buffer+2); 278 | #if CY_PSOC3 279 | copied = CYSWAP_ENDIAN16(copied); 280 | #endif 281 | got_sop = (buffer[0] == 0x01); 282 | got_eop = (buffer[(*count)-1] == 0x17); 283 | // Got start and end of packet, check that we have 284 | // SOP + command + 2 bytes data length + payload + 2 bytes checksum + eop 285 | // Reuse variable 286 | frame_id = 1 + 1 + 2 + copied + 2 + 1; 287 | if ((*count == frame_id) && (got_sop) && (got_eop)) { 288 | return CYRET_SUCCESS; 289 | } 290 | 291 | } // for mailboxes loop ------------------------------------------------- 292 | 293 | // Wait a little if all the mailboxes in the last scan were empty 294 | if (timeout && !full_mailboxes) { 295 | CyDelay(WAIT_STEP_MS); 296 | timeout_ms -= WAIT_STEP_MS; 297 | } 298 | } while (timeout_ms >= 0); 299 | 300 | got_sop = (buffer[0] == 0x01); 301 | got_eop = (buffer[(*count)-1] == 0x17); 302 | frame_id = 1 + 1 + 2 + copied + 2 + 1; 303 | if ((*count == frame_id) && (got_sop) && (got_eop)) { 304 | return CYRET_SUCCESS; 305 | } 306 | 307 | return CYRET_TIMEOUT; 308 | } 309 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cyflash 2 | ======= 3 | 4 | Cyflash is a tool for uploading firmware to Cypress PSoC devices via Cypress's 5 | standard bootloader protocol. 6 | 7 | Basic usage is simple: specify an interface to connect to the device via 8 | (currently only serial is supported) and a .cyacd file to upload, and cyflash 9 | does the rest. 10 | 11 | Cyflash also has the advantage of being about 5 times faster than Cypress's 12 | tool, being cross-platform, and not requiring all of PSoC creator to work. 13 | 14 | Install cyflash from pypi with `pip install cyflash`, or (from source) 15 | `python setup.py install`. 16 | 17 | Example command line: 18 | 19 | cyflash --serial=/dev/tty.usb-device myfirmware.cyacd 20 | 21 | Example output: 22 | 23 | Initialising bootloader. 24 | Silicon ID 0x04a61193, revision 17. 25 | Array 0: first row 22, last row 255. 26 | Device application_id 0, version 258. 27 | Uploading data (198/198) 28 | Device checksum verifies OK. 29 | Rebooting device. 30 | 31 | If cyflash detects a valid metadata record on the device already, it will read 32 | and compare this to your image's metadata. By default, cyflash will prompt you 33 | before overwriting the firmware with an older version or one with a different 34 | application ID. You can force this behaviour with --downgrade or --nodowngrade 35 | and --newapp and --nonewapp, respectively. 36 | 37 | Cyflash is still quite new, and should be considered beta-quality software. 38 | Pull requests and bug reports are most welcome. 39 | 40 | 41 | Cypress Bootloader metadata component bug 42 | ========================================= 43 | Bootloader component v1.40 and 1.50 have a bug that prevents the GET_METADATA 44 | command to work correctly. The #define Bootloader_RSP_SIZE_GET_METADATA (0x56u) 45 | in Bootloader_PVT.h should be #define Bootloader_RSP_SIZE_GET_METADATA (56u) 46 | Version 1.60 should resolve this issue 47 | 48 | CANbus as transport 49 | =================== 50 | cyflash can use raw CANbus frames as transport with the python-can library. 51 | On the target side please see the CANbus_Bootloader.c file that implements 52 | the Cypress boodloader communication interface. 53 | -------------------------------------------------------------------------------- /cyflash/__init__.py: -------------------------------------------------------------------------------- 1 | from . import bootload 2 | from . import cyacd 3 | from . import protocol 4 | -------------------------------------------------------------------------------- /cyflash/__main__.py: -------------------------------------------------------------------------------- 1 | from . import bootload 2 | 3 | bootload.main() 4 | -------------------------------------------------------------------------------- /cyflash/bootload.py: -------------------------------------------------------------------------------- 1 | """PSoC bootloader command line tool.""" 2 | 3 | import argparse 4 | import codecs 5 | import time 6 | import six 7 | import sys 8 | 9 | from builtins import input 10 | 11 | from . import cyacd 12 | from . import protocol 13 | 14 | 15 | __version__ = "1.07" 16 | 17 | 18 | def auto_int(x): 19 | return int(x, 0) 20 | 21 | parser = argparse.ArgumentParser(description="Bootloader tool for Cypress PSoC devices") 22 | 23 | group = parser.add_mutually_exclusive_group(required=True) 24 | group.add_argument( 25 | '--serial', 26 | action='store', 27 | dest='serial', 28 | metavar='PORT', 29 | default=None, 30 | help="Use a serial interface") 31 | group.add_argument( 32 | '--canbus', 33 | action='store', 34 | dest='canbus', 35 | metavar='BUSTYPE', 36 | default=None, 37 | help="Use a CANbus interface (requires python-can)") 38 | 39 | parser.add_argument( 40 | '--serial_baudrate', 41 | action='store', 42 | dest='serial_baudrate', 43 | metavar='BAUD', 44 | default=115200, 45 | type=int, 46 | help="Baud rate to use when flashing using serial (default 115200)") 47 | parser.add_argument( 48 | '--parity', 49 | action='store', 50 | default='None', 51 | type=str, 52 | help="Desired parity (e.g. None, Even, Odd, Mark, or Space)") 53 | parser.add_argument( 54 | '--stopbits', 55 | action='store', 56 | default='1', 57 | type=str, 58 | help="Desired stop bits (e.g. 1, 1.5, or 2)") 59 | parser.add_argument( 60 | '--dtr', 61 | action='store_true', 62 | help="set DTR state true (default false)") 63 | parser.add_argument( 64 | '--rts', 65 | action='store_true', 66 | help="set RTS state true (default false)") 67 | parser.add_argument( 68 | '--canbus_baudrate', 69 | action='store', 70 | dest='canbus_baudrate', 71 | metavar='BAUD', 72 | default=125000, 73 | type=int, 74 | help="Baud rate to use when flashing using CANbus (default 125000)") 75 | parser.add_argument( 76 | '--canbus_channel', 77 | action='store', 78 | dest='canbus_channel', 79 | metavar='CANBUS_CHANNEL', 80 | default=0, 81 | help="CANbus channel to be used") 82 | parser.add_argument( 83 | '--canbus_id', 84 | action='store', 85 | dest='canbus_id', 86 | metavar='CANBUS_ID', 87 | default=0, 88 | type=auto_int, 89 | help="CANbus frame ID to be used") 90 | 91 | group = parser.add_mutually_exclusive_group(required=False) 92 | group.add_argument( 93 | '--canbus_echo', 94 | action='store_true', 95 | dest='canbus_echo', 96 | default=False, 97 | help="Use echoed back received CAN frames to keep the host in sync") 98 | group.add_argument( 99 | '--canbus_wait', 100 | action='store', 101 | dest='canbus_wait', 102 | metavar='CANBUS_WAIT', 103 | default=5, 104 | type=int, 105 | help="Wait for CANBUS_WAIT ms amount of time after sending a frame if you're not using echo frames as a way to keep host in sync") 106 | 107 | parser.add_argument( 108 | '--timeout', 109 | action='store', 110 | dest='timeout', 111 | metavar='SECS', 112 | default=5.0, 113 | type=float, 114 | help="Time to wait for a Bootloader response (default 5)") 115 | 116 | group = parser.add_mutually_exclusive_group() 117 | group.add_argument( 118 | '--downgrade', 119 | action='store_true', 120 | dest='downgrade', 121 | default=None, 122 | help="Don't prompt before flashing old firmware over newer") 123 | group.add_argument( 124 | '--nodowngrade', 125 | action='store_false', 126 | dest='downgrade', 127 | default=None, 128 | help="Fail instead of prompting when device firmware is newer") 129 | 130 | group = parser.add_mutually_exclusive_group() 131 | group.add_argument( 132 | '--newapp', 133 | action='store_true', 134 | dest='newapp', 135 | default=None, 136 | help="Don't prompt before flashing an image with a different application ID") 137 | group.add_argument( 138 | '--nonewapp', 139 | action='store_false', 140 | dest='newapp', 141 | default=None, 142 | help="Fail instead of flashing an image with a different application ID") 143 | 144 | parser.add_argument( 145 | 'logging_config', 146 | action='store', 147 | type=argparse.FileType(mode='r'), 148 | nargs='?', 149 | help="Python logging configuration file") 150 | 151 | parser.add_argument( 152 | '--psoc5', 153 | action='store_true', 154 | dest='psoc5', 155 | default=False, 156 | help="Add tag to parse PSOC5 metadata") 157 | 158 | def validate_key(string): 159 | if len(string) != 14: 160 | raise argparse.ArgumentTypeError("key is of unexpected length") 161 | 162 | try: 163 | val = int(string, base=16) 164 | key = [] 165 | key.append((val >> 40) & 0xff) 166 | key.append((val >> 32) & 0xff) 167 | key.append((val >> 24) & 0xff) 168 | key.append((val >> 16) & 0xff) 169 | key.append((val >> 8) & 0xff) 170 | key.append(val & 0xff) 171 | return key 172 | except ValueError: 173 | raise argparse.ArgumentTypeError("key is of unexpected format") 174 | 175 | parser.add_argument( 176 | '--key', 177 | action='store', 178 | dest='key', 179 | default=None, 180 | type=validate_key, 181 | help="Optional security key (six bytes, on the form 0xAABBCCDDEEFF)") 182 | 183 | DEFAULT_CHUNKSIZE = 25 184 | parser.add_argument( 185 | '-cs', 186 | '--chunk-size', 187 | action='store', 188 | dest='chunk_size', 189 | default=DEFAULT_CHUNKSIZE, 190 | type=int, 191 | help="Chunk size to use for transfers - default %d" % DEFAULT_CHUNKSIZE) 192 | 193 | parser.add_argument( 194 | '--dual-app', 195 | action='store_true', 196 | dest='dual_app', 197 | default=False, 198 | help="The bootloader is dual-application - will mark the newly flashed app as active") 199 | 200 | parser.add_argument( 201 | '-v', 202 | '--verbose', 203 | action='store_true', 204 | dest='verbose', 205 | default=False, 206 | help="Enable verbose debug output") 207 | 208 | parser.add_argument( 209 | 'image', 210 | action='store', 211 | type=argparse.FileType(mode='r'), 212 | help="Image to read flash data from") 213 | 214 | checksum_types = { 215 | 0: protocol.sum_2complement_checksum, 216 | 1: protocol.crc16_checksum, 217 | } 218 | 219 | 220 | class BootloaderError(Exception): pass 221 | 222 | 223 | def make_session(args, checksum_type): 224 | if args.serial: 225 | import serial 226 | ser = serial.Serial() 227 | ser.port = args.serial 228 | ser.baudrate = args.serial_baudrate 229 | ser.parity = parity_convert(args.parity) 230 | mapping = {"1": serial.STOPBITS_ONE, 231 | "1.5": serial.STOPBITS_ONE_POINT_FIVE, 232 | "2": serial.STOPBITS_TWO, 233 | } 234 | if not args.stopbits in mapping: 235 | print('\nillegal argument', args.stopbits, 'for stopbit using ONE STOPBIT instead\n') 236 | ser.stopbits = mapping.get(args.stopbits, serial.STOPBITS_ONE) 237 | ser.timeout = args.timeout 238 | ser.rts = args.dtr 239 | ser.dtr = args.rts 240 | ser.open() 241 | ser.flushInput() # need to clear any garbage off the serial port 242 | ser.flushOutput() 243 | transport = protocol.SerialTransport(ser, args.verbose) 244 | elif args.canbus: 245 | import can 246 | # Remaining configuration options should follow python-can practices 247 | canbus = can.interface.Bus(bustype=args.canbus, channel=args.canbus_channel, bitrate=args.canbus_baudrate) 248 | # Wants timeout in ms, we have it in s 249 | transport = protocol.CANbusTransport(canbus, args.canbus_id, int(args.timeout * 1000), args.canbus_echo, 250 | args.canbus_wait) 251 | transport.MESSAGE_CLASS = can.Message 252 | else: 253 | raise BootloaderError("No valid interface specified") 254 | 255 | try: 256 | checksum_func = checksum_types[checksum_type] 257 | except KeyError: 258 | raise BootloaderError("Invalid checksum type: %d" % (checksum_type,)) 259 | 260 | return protocol.BootloaderSession(transport, checksum_func) 261 | 262 | 263 | def seek_permission(argument, message): 264 | if argument is not None: 265 | return lambda remote, local: argument 266 | else: 267 | def prompt(*args): 268 | while True: 269 | result = input(message % args) 270 | if result.lower().startswith('y'): 271 | return True 272 | elif result.lower().startswith('n'): 273 | return False 274 | return prompt 275 | 276 | 277 | class BootloaderHost(object): 278 | def __init__(self, session, args, out): 279 | self.session = session 280 | self.key = args.key 281 | self.chunk_size = args.chunk_size 282 | self.dual_app = args.dual_app 283 | self.out = out 284 | self.row_ranges = {} 285 | 286 | def bootload(self, data, downgrade, newapp, psoc5): 287 | self.out.write("Entering bootload.\n") 288 | self.enter_bootloader(data) 289 | if self.dual_app: 290 | self.out.write("Getting application status.\n") 291 | app_area_to_flash = self.application_status() 292 | self.out.write("Verifying row ranges.\n") 293 | self.verify_row_ranges(data) 294 | self.out.write("Checking metadata.\n") 295 | self.check_metadata(data, downgrade, newapp, psoc5) 296 | self.out.write("Starting flash operation.\n") 297 | self.write_rows(data) 298 | if not self.session.verify_checksum(): 299 | raise BootloaderError("Flash checksum does not verify! Aborting.") 300 | else: 301 | self.out.write("Device checksum verifies OK.\n") 302 | if self.dual_app: 303 | self.set_application_active(app_area_to_flash) 304 | self.out.write("Rebooting device.\n") 305 | self.session.exit_bootloader() 306 | 307 | def set_application_active(self, application_id): 308 | self.out.write("Setting application %d as active.\n" % application_id) 309 | self.session.set_application_active(application_id) 310 | 311 | def application_status(self): 312 | to_flash = None 313 | for app in [0, 1]: 314 | app_valid, app_active = self.session.application_status(app) 315 | self.out.write("App %d: valid: %s, active: %s\n" % (app, app_valid, app_active)) 316 | if app_active == 0: 317 | to_flash = app 318 | 319 | if to_flash is None: 320 | raise BootloaderError("Failed to find inactive app to flash. Aborting.") 321 | self.out.write("Will flash app %d.\n" % to_flash) 322 | return to_flash 323 | 324 | def verify_row_ranges(self, data): 325 | for array_id, array in six.iteritems(data.arrays): 326 | start_row, end_row = self.session.get_flash_size(array_id) 327 | self.out.write("Array %d: first row %d, last row %d.\n" % ( 328 | array_id, start_row, end_row)) 329 | self.row_ranges[array_id] = (start_row, end_row) 330 | for row_number in array: 331 | if row_number < start_row or row_number > end_row: 332 | raise BootloaderError( 333 | "Row %d in array %d out of range. Aborting." 334 | % (row_number, array_id)) 335 | 336 | def enter_bootloader(self, data): 337 | self.out.write("Initialising bootloader.\n") 338 | silicon_id, silicon_rev, bootloader_version = self.session.enter_bootloader(self.key) 339 | self.out.write("Silicon ID 0x%.8x, revision %d.\n" % (silicon_id, silicon_rev)) 340 | if silicon_id != data.silicon_id: 341 | raise ValueError("Silicon ID of device (0x%.8x) does not match firmware file (0x%.8x)" 342 | % (silicon_id, data.silicon_id)) 343 | if silicon_rev != data.silicon_rev: 344 | raise ValueError("Silicon revision of device (0x%.2x) does not match firmware file (0x%.2x)" 345 | % (silicon_rev, data.silicon_rev)) 346 | 347 | def check_metadata(self, data, downgrade, newapp, psoc5): 348 | try: 349 | if psoc5: 350 | metadata = self.session.get_psoc5_metadata(0) 351 | else: 352 | metadata = self.session.get_metadata(0) 353 | self.out.write("Device application_id %d, version %d.\n" % ( 354 | metadata.app_id, metadata.app_version)) 355 | except protocol.InvalidApp: 356 | self.out.write("No valid application on device.\n") 357 | return 358 | except protocol.BootloaderError as e: 359 | self.out.write("Cannot read metadata from device: {}\n".format(e)) 360 | return 361 | 362 | # TODO: Make this less horribly hacky 363 | # Fetch from last row of last flash array 364 | metadata_row = data.arrays[max(data.arrays.keys())][self.row_ranges[max(data.arrays.keys())][1]] 365 | if psoc5: 366 | local_metadata = protocol.GetPSOC5MetadataResponse(metadata_row.data[192:192+56]) 367 | else: 368 | local_metadata = protocol.GetMetadataResponse(metadata_row.data[64:120]) 369 | 370 | if metadata.app_version > local_metadata.app_version: 371 | message = "Device application version is v%d.%d, but local application version is v%d.%d." % ( 372 | metadata.app_version >> 8, metadata.app_version & 0xFF, 373 | local_metadata.app_version >> 8, local_metadata.app_version & 0xFF) 374 | if not downgrade(metadata.app_version, local_metadata.app_version): 375 | raise ValueError(message + " Aborting.") 376 | 377 | if metadata.app_id != local_metadata.app_id: 378 | message = "Device application ID is %d, but local application ID is %d." % ( 379 | metadata.app_id, local_metadata.app_id) 380 | if not newapp(metadata.app_id, local_metadata.app_id): 381 | raise ValueError(message + " Aborting.") 382 | 383 | def write_rows(self, data): 384 | total = sum(len(x) for x in data.arrays.values()) 385 | i = 0 386 | nb_of_tries = 3 387 | for array_id, array in six.iteritems(data.arrays): 388 | for row_number, row in array.items(): 389 | tries = nb_of_tries 390 | while tries: 391 | actual_checksum = -1 392 | try: 393 | self.session.program_row(array_id, row_number, row.data, self.chunk_size) 394 | actual_checksum = self.session.get_row_checksum(array_id, row_number) 395 | except Exception as e: 396 | self.out.write("\nwill retry " + str(e) + "\n") 397 | # try to read if there is data left! 398 | self.session.transport.f.read(10) 399 | 400 | if actual_checksum == row.checksum: 401 | i += 1 402 | self.progress("Uploading data", i, total) 403 | break 404 | else: 405 | tries = tries - 1 406 | if tries == 0: 407 | raise BootloaderError( 408 | "Checksum does not match in array %d row %d. Expected %.2x, got %.2x! Aborting; tried %d times" % ( 409 | array_id, row_number, row.checksum, actual_checksum, nb_of_tries)) 410 | self.progress() 411 | 412 | def progress(self, message=None, current=None, total=None): 413 | if not message: 414 | self.out.write("\n") 415 | else: 416 | self.out.write("\r%s (%d/%d)" % (message, current, total)) 417 | self.out.flush() 418 | 419 | def parity_convert(value): 420 | import serial 421 | if value.lower() in ("none", "n"): 422 | parity = serial.PARITY_NONE 423 | elif value.lower() in ("even", "e"): 424 | parity = serial.PARITY_EVEN 425 | elif value.lower() in ("odd", "o"): 426 | parity = serial.PARITY_ODD 427 | else: 428 | parity = serial.PARITY_NONE 429 | print('\nillegal argument', value, 'for parity using', parity, 'instead\n') 430 | 431 | return parity 432 | 433 | def main(): 434 | args = parser.parse_args() 435 | 436 | if (args.logging_config): 437 | import logging 438 | import logging.config 439 | logging.config.fileConfig(args.logging_config) 440 | 441 | if (six.PY3): 442 | t0 = time.perf_counter() 443 | else: 444 | t0 = time.clock() 445 | data = cyacd.BootloaderData.read(args.image) 446 | session = make_session(args, data.checksum_type) 447 | bl = BootloaderHost(session, args, sys.stdout) 448 | try: 449 | bl.bootload( 450 | data, 451 | seek_permission( 452 | args.downgrade, 453 | "Device version %d is greater than local version %d. Flash anyway? (Y/N)"), 454 | seek_permission( 455 | args.newapp, 456 | "Device app ID %d is different from local app ID %d. Flash anyway? (Y/N)"), 457 | args.psoc5) 458 | except (protocol.BootloaderError, BootloaderError) as e: 459 | print("Unhandled error: {}".format(e)) 460 | return 1 461 | if (six.PY3): 462 | t1 = time.perf_counter() 463 | else: 464 | t1 = time.clock() 465 | print("Total running time {0:02.2f}s".format(t1 - t0)) 466 | return 0 467 | 468 | 469 | if __name__ == '__main__': 470 | sys.exit(main()) 471 | -------------------------------------------------------------------------------- /cyflash/cyacd.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import six 3 | import struct 4 | 5 | hex_decoder = codecs.getdecoder('hex') 6 | 7 | 8 | class BootloaderRow(object): 9 | def __init__(self): 10 | self.array_id = None 11 | self.row_number = None 12 | self.data = None 13 | 14 | @classmethod 15 | def read(cls, data, line=None): 16 | self = cls() 17 | if data[0] != ':': 18 | raise ValueError("Bootloader rows must start with a colon") 19 | data = hex_decoder(data[1:])[0] 20 | self.array_id, self.row_number, data_length = struct.unpack('>BHH', data[:5]) 21 | self.data = data[5:-1] 22 | if len(self.data) != data_length: 23 | raise ValueError("Row specified %d bytes of data, but got %d" 24 | % (data_length, len(self.data))) 25 | # data is already a bytes object in Py3 26 | if (six.PY2): 27 | (checksum,) = struct.unpack('B', data[-1]) 28 | data_checksum = 0x100 - (sum(ord(x) for x in data[:-1]) & 0xFF) 29 | elif (six.PY3): 30 | checksum = data[-1] 31 | data_checksum = 0x100 - (sum(data[:-1]) & 0xFF) 32 | 33 | if data_checksum == 0x100: 34 | data_checksum = 0 35 | if checksum != data_checksum: 36 | raise ValueError("Computed checksum of 0x%.2x, but expected 0x%.2x on line %d" 37 | % (data_checksum, checksum, line)) 38 | return self 39 | 40 | @property 41 | def checksum(self): 42 | """Returns the data checksum. Should match what the bootloader returns.""" 43 | # Python2 44 | if (six.PY2): 45 | return 0xFF & (1 + ~sum(ord(x) for x in self.data)) 46 | 47 | return (1 + ~sum(self.data)) & 0xFF 48 | 49 | 50 | class BootloaderData(object): 51 | def __init__(self): 52 | self.silicon_id = None 53 | self.silicon_rev = None 54 | self.checksum_type = None 55 | self.arrays = {} 56 | self.total_rows = 0 57 | 58 | @classmethod 59 | def read(cls, f): 60 | if (six.PY2): 61 | header = f.readline().strip().decode('hex') 62 | elif (six.PY3): 63 | # header is a bytes instance 64 | header = hex_decoder(f.readline().strip())[0] 65 | 66 | if len(header) != 6: 67 | raise ValueError("Expected 12 byte header line first, firmware file may be corrupt.") 68 | self = cls() 69 | self.silicon_id, self.silicon_rev, self.checksum_type = struct.unpack('>LBB', header) 70 | for i, line in enumerate(f): 71 | row = BootloaderRow.read(line.strip(), i + 2) 72 | if row.array_id not in self.arrays: 73 | self.arrays[row.array_id] = {} 74 | self.arrays[row.array_id][row.row_number] = row 75 | self.total_rows += row.row_number; 76 | return self 77 | 78 | def __str__(self): 79 | x = "Silicon ID {0.silicon_id}, Silicon Rev. {0.silicon_rev}, Checksum type {0.checksum_type}, Arrays {1} total rows {0.total_rows}".format( 80 | self, len(self.arrays) 81 | ) 82 | return x 83 | -------------------------------------------------------------------------------- /cyflash/cyacd_test.py: -------------------------------------------------------------------------------- 1 | from cStringIO import StringIO 2 | import unittest 3 | 4 | import cyacd 5 | 6 | 7 | class BootloaderRowTest(unittest.TestCase): 8 | def testParseRow(self): 9 | rowdata = ":000018008000100020110C0000E92D0000E92D000008B5024B83F3088802F0E8F800100020F8B572B6002406236343704D0134EE187279707831793778B3781202F67800020A4338431904084337063843002103F09FF8032CE7D1291C12316548802203F08EF80023191C634AFF25141C143418593C32061CAE434F00C4B2351CD219002CB8" 10 | blrow = cyacd.BootloaderRow.read(rowdata) 11 | self.assertEquals(blrow.array_id, 0) 12 | self.assertEquals(blrow.row_number, 0x18) 13 | self.assertEquals(len(blrow.data), 0x80) 14 | self.assertEquals(blrow.data.encode('hex').upper(), rowdata[11:-2]) 15 | 16 | def testParseFile(self): 17 | filedata = """04A611931101 18 | :000018008000100020110C0000E92D0000E92D000008B5024B83F3088802F0E8F800100020F8B572B6002406236343704D0134EE187279707831793778B3781202F67800020A4338431904084337063843002103F09FF8032CE7D1291C12316548802203F08EF80023191C634AFF25141C143418593C32061CAE434F00C4B2351CD219002CB8 19 | :000019008007D0167857787619013C3770E4B20232F5E7C0B204330918282BE4D1564A574B574C584D584F59491A6099262C604F20574A584B584D3E600F240860574F58491A6003262C608720564B574C3E603C220860564DD82756491A6038012260554A2E60554B0860554C554D56481660C0270E2655491E6002222C6093260760534BB3""" 20 | bldata = cyacd.BootloaderData.read(StringIO(filedata)) 21 | self.assertEquals(bldata.silicon_id, 0x04A61193) 22 | self.assertEquals(bldata.silicon_rev, 0x11) 23 | self.assertEquals(bldata.checksum_type, 0x01) 24 | self.assertEquals(len(bldata.rows), 2) 25 | self.assertTrue(all(isinstance(row, cyacd.BootloaderRow) for row in bldata.rows)) 26 | 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /cyflash/protocol.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import six 3 | from builtins import super 4 | from builtins import range 5 | import struct 6 | import time 7 | 8 | 9 | class InvalidPacketError(Exception): 10 | pass 11 | 12 | 13 | class BootloaderError(Exception): 14 | pass 15 | 16 | 17 | class BootloaderTimeoutError(BootloaderError): 18 | pass 19 | 20 | 21 | # TODO: Implement Security key functionality 22 | class BootloaderKeyError(BootloaderError): 23 | STATUS = 0x01 24 | 25 | def __init__(self): 26 | super().__init__("The provided security key was incorrect") 27 | 28 | 29 | class VerificationError(BootloaderError): 30 | STATUS = 0x02 31 | 32 | def __init__(self): 33 | super().__init__("The flash verification failed.") 34 | 35 | 36 | class IncorrectLength(BootloaderError): 37 | STATUS = 0x03 38 | 39 | def __init__(self): 40 | super().__init__("The amount of data available is outside the expected range") 41 | 42 | 43 | class InvalidData(BootloaderError): 44 | STATUS = 0x04 45 | 46 | def __init__(self): 47 | super().__init__("The data is not of the proper form") 48 | 49 | 50 | class InvalidCommand(BootloaderError): 51 | STATUS = 0x05 52 | 53 | def __init__(self): 54 | super().__init__("Command unsupported on target device") 55 | 56 | 57 | class UnexpectedDevice(BootloaderError): 58 | STATUS = 0x06 59 | 60 | 61 | class UnsupportedBootloaderVersion(BootloaderError): 62 | STATUS = 0x07 63 | 64 | 65 | class InvalidChecksum(BootloaderError): 66 | STATUS = 0x08 67 | 68 | 69 | class InvalidArray(BootloaderError): 70 | STATUS = 0x09 71 | 72 | 73 | class InvalidFlashRow(BootloaderError): 74 | STATUS = 0x0A 75 | 76 | 77 | class ProtectedFlash(BootloaderError): 78 | STATUS = 0x0B 79 | 80 | 81 | class InvalidApp(BootloaderError): 82 | STATUS = 0x0C 83 | 84 | 85 | class TargetApplicationIsActive(BootloaderError): 86 | STATUS = 0x0D 87 | 88 | def __init__(self): 89 | super().__init__("The application is currently marked as active or golden image") 90 | 91 | 92 | class CallbackResponseInvalid(BootloaderError): 93 | STATUS = 0x0E 94 | 95 | 96 | class UnknownError(BootloaderError): 97 | STATUS = 0x0F 98 | 99 | 100 | class BootloaderResponse(object): 101 | FORMAT = "" 102 | ARGS = () 103 | 104 | ERRORS = {klass.STATUS: klass for klass in [ 105 | BootloaderKeyError, 106 | VerificationError, 107 | IncorrectLength, 108 | InvalidData, 109 | InvalidCommand, 110 | InvalidChecksum, 111 | UnexpectedDevice, 112 | UnsupportedBootloaderVersion, 113 | InvalidArray, 114 | InvalidFlashRow, 115 | ProtectedFlash, 116 | InvalidApp, 117 | TargetApplicationIsActive, 118 | CallbackResponseInvalid, 119 | UnknownError 120 | ]} 121 | 122 | def __init__(self, data): 123 | try: 124 | unpacked = struct.unpack(self.FORMAT, data) 125 | except struct.error as e: 126 | raise InvalidPacketError("Cannot unpack packet data '{}': {}".format(data, e)) 127 | for arg, value in zip(self.ARGS, unpacked): 128 | if arg: 129 | setattr(self, arg, value) 130 | 131 | @classmethod 132 | def decode(cls, data, checksum_func): 133 | start, status, length = struct.unpack(" 8): 467 | msg = self.MESSAGE_CLASS( 468 | extended_id=False, 469 | arbitration_id=self.frame_id, 470 | data=data[start:start + 8] 471 | ) 472 | else: 473 | msg = self.MESSAGE_CLASS( 474 | extended_id=False, 475 | arbitration_id=self.frame_id, 476 | data=data[start:] 477 | ) 478 | 479 | # Flush input mailbox(es) 480 | while (self.transport.recv(timeout=0)): 481 | pass 482 | 483 | self.transport.send(msg) 484 | self._last_sent_frame = msg 485 | if (self.echo_frames): 486 | # Read back the echo message 487 | while (True): 488 | frame = self.transport.recv(self.timeout) 489 | if (not frame): 490 | raise BootloaderTimeoutError("Did not receive echo frame within {} timeout".format(self.timeout)) 491 | # Don't check the frame arbitration ID, it may be used for varying purposes 492 | if (frame.data[:frame.dlc] != msg.data[:msg.dlc]): 493 | continue 494 | # Ok, got a good frame 495 | break 496 | elif (self.wait_send_s > 0.0): 497 | time.sleep(self.wait_send_s) 498 | 499 | start += 8 500 | 501 | def recv(self): 502 | # Response packets read from the Bootloader have the following structure: 503 | # Start of Packet (0x01): 1 byte 504 | # Status Code: 1 byte 505 | # Data Length: 2 bytes 506 | # Data: N bytes of data 507 | # Checksum: 2 bytes 508 | # End of Packet (0x17): 1 byte 509 | 510 | data = bytearray() 511 | # Read first frame, contains data length 512 | while True: 513 | frame = self.transport.recv(self.timeout) 514 | if (not frame): 515 | raise BootloaderTimeoutError("Timed out waiting for Bootloader 1st response frame") 516 | 517 | if frame.arbitration_id != self.frame_id: 518 | continue 519 | 520 | # Don't check the frame arbitration ID, it may be used for varying purposes 521 | 522 | if len(frame.data) < 4: 523 | raise BootloaderTimeoutError("Unexpected response data: length {}, minimum is 4".format(len(frame.data))) 524 | 525 | if (frame.data[0] != 0x01): 526 | raise BootloaderTimeoutError("Unexpected start of frame data: 0x{0:02X}, expected 0x01".format(frame.data[0])) 527 | 528 | break 529 | 530 | data += frame.data[:frame.dlc] 531 | 532 | # 4 initial bytes, reported size, 3 tail 533 | total_size = 4 + (struct.unpack("> 1) ^ 0x8408 557 | else: 558 | crc >>= 1 559 | b >>= 1 560 | 561 | crc = (crc << 8) | (crc >> 8) 562 | return ~crc & 0xffff 563 | 564 | 565 | def sum_2complement_checksum(data): 566 | if (type(data) is str): 567 | return (1 + ~sum([ord(c) for c in data])) & 0xFFFF 568 | elif (type(data) in (bytearray, bytes)): 569 | return (1 + ~sum(data)) & 0xFFFF 570 | -------------------------------------------------------------------------------- /logging.conf.example: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root,can 3 | 4 | [handlers] 5 | keys=fileHandler 6 | 7 | [formatters] 8 | keys=basicFormatter 9 | 10 | [logger_root] 11 | level=DEBUG 12 | handlers= 13 | 14 | [logger_can] 15 | level=DEBUG 16 | handlers=fileHandler 17 | qualname=can 18 | 19 | [handler_fileHandler] 20 | class=handlers.RotatingFileHandler 21 | formatter=basicFormatter 22 | level=DEBUG 23 | args=('can.log', 'a', 10*1024*1024, 10) 24 | 25 | [formatter_basicFormatter] 26 | class=logging.Formatter 27 | format=%(asctime)s.%(msecs)03d|%(levelname)-8s|%(name)s|%(filename)s:%(lineno)d|%(message)s 28 | datefmt=%Y-%m-%d %H:%M:%S -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from setuptools import setup 3 | 4 | version = re.search( 5 | '^__version__\s*=\s*"(.*)"', 6 | open('cyflash/bootload.py').read(), 7 | re.M 8 | ).group(1) 9 | 10 | with open("README.md", "rb") as f: 11 | long_descr = f.read().decode("utf-8") 12 | 13 | setup( 14 | name="cyflash", 15 | packages=["cyflash"], 16 | entry_points={ 17 | "console_scripts": ['cyflash = cyflash.bootload:main'] 18 | }, 19 | version=version, 20 | description="Tool for flashing data to Cypress PSoC devices via bootloader.", 21 | long_description=long_descr, 22 | author="Nick Johnson", 23 | author_email="nick@arachnidlabs.com", 24 | url="http://github.com/arachnidlabs/cyflash/", 25 | install_requires=["pyserial", "six>=1.10", "future"], 26 | extras_require={ 27 | 'CANbus': ["python-can>=1.4"] 28 | }, 29 | include_package_data=True, 30 | ) 31 | --------------------------------------------------------------------------------