diff --git a/.gitignore b/.gitignore index e2fc363..4dedbc4 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,8 @@ target/ *.swp *.swo */*.swo + +# ignore moved examples +bin/get_ecu_block.py + +*/genkey.py diff --git a/README.rst b/README.rst index 651549d..424c348 100644 --- a/README.rst +++ b/README.rst @@ -12,6 +12,8 @@ physical layer is used between the tractor and trailer, specified in ISO 11992. This package is dependent on, was a part of, and broken out from, the `python-can `__ project that Brian Thorne has maintained for years.. +This codce currently is compatable with the python-can version 3.3.2. After you clone the python-can repo be sure to checkout the 'release-3.3.2' branch + The **C**\ ontroller **A**\ rea **N**\ etwork is a bus standard designed to allow microcontrollers and devices to communicate with each other. It has priority based bus arbitration, reliable deterministic diff --git a/bin/j1939_logger.py b/bin/j1939_logger.py index f83f68b..0662345 100644 --- a/bin/j1939_logger.py +++ b/bin/j1939_logger.py @@ -9,7 +9,7 @@ import j1939 import logging -lLevel = logging.WARNING +lLevel = logging.WARN logger = logging.getLogger() if 1: @@ -20,8 +20,6 @@ ch.setFormatter(chformatter) logger.addHandler(ch) - - def parse_arguments(): parser = argparse.ArgumentParser( description=textwrap.dedent("""\ @@ -34,7 +32,7 @@ def parse_arguments(): a configuration file - see the README for detail. """), epilog="""Pull requests and issues - https://bitbucket.org/hardbyte/python-can""", + https://github.com/hardbyte/python-can""", formatter_class=argparse.RawTextHelpFormatter ) @@ -95,7 +93,8 @@ def parse_arguments(): ''')) parser.add_argument('-i', '--interface', dest="interface", - #choices=can.interface.VALID_INTERFACES, + default='socketcan', + #choices=can.interfaces.VALID_INTERFACES, help=textwrap.dedent('''\ Specify the backend CAN interface to use. @@ -103,28 +102,24 @@ def parse_arguments(): {} Alternatively the CAN_INTERFACE environment variable can be set. - '''.format(can.interface.VALID_INTERFACES))) + '''.format(can.interfaces.VALID_INTERFACES))) return parser.parse_args() if __name__ == "__main__": - args = parse_arguments() verbosity = args.verbosity logging_level_name = ['critical', 'error', 'warning', 'info', 'debug', 'subdebug'][min(5, verbosity)] can.set_logging_level(logging_level_name) - from can.interfaces.interface import * - filters = [] if args.pgn is not None: print('Have to filter pgns: ', args.pgn) for pgn in args.pgn: if pgn.startswith('0x'): pgn = int(pgn[2:], base=16) - filters.append({'pgn': int(pgn)}) if args.source is not None: for src in args.source: @@ -135,7 +130,6 @@ def parse_arguments(): filters = json.load(args.filter) print("Loaded filters from file: ", filters) - print("args.channel : ", args.channel) print("args.interface: ", args.interface) print("filter PGN's : ", args.pgn) @@ -143,6 +137,7 @@ def parse_arguments(): print("filters : ", filters) bus = j1939.Bus(channel=args.channel, bustype=args.interface, j1939_filters=filters, timeout=0.1) + print("channel info : ", bus.can_bus.channel_info) log_start_time = datetime.datetime.now() print('can.j1939 logger started on {}\n'.format(log_start_time)) logger.info('can.j1939 logger started on {}\n'.format(log_start_time)) diff --git a/bin/j1939_mem_query.py b/bin/j1939_mem_query.py index 98afca2..e14931f 100644 --- a/bin/j1939_mem_query.py +++ b/bin/j1939_mem_query.py @@ -1,71 +1,118 @@ -from __future__ import print_function +#!/usr/bin/python +# -import can -import j1939 -import time -import threading +_name = "Simple J1939 memory object query" +__version__ = "1.1.0" +__date__ = "3/25/22" +__exp__ = "()" # (Release Version) +title = "%s Version: %s %s %s" % (_name, __version__, __date__, __exp__) -import logging +import sys +MIN_PYTHON = (3, 5) +if sys.version_info < MIN_PYTHON: + sys.exit("Python %s.%s or later is required.\n" % MIN_PYTHON) -lLevel = logging.DEBUG +import j1939.utils + +if __name__ == "__main__": + # import traceback + # import timeit + # import time + # import textwrap + # import inspect + import argparse + import logging -logger = logging.getLogger() -logger.setLevel(lLevel) -ch = logging.StreamHandler() -fh = logging.FileHandler('/tmp/j1939_nodes.log') -fh.setLevel(lLevel) -ch.setLevel(lLevel) -formatter = logging.Formatter('%(asctime)s | %(name)20s | %(threadName)20s | %(levelname)5s | %(message)s') -chformatter = logging.Formatter('%(name)25s | %(threadName)10s | %(levelname)5s | %(message)s') -fh.setFormatter(formatter) -ch.setFormatter(chformatter) -logger.addHandler(ch) -logger.addHandler(fh) if __name__ == "__main__": - #from can.interfaces.interface import * - filters = [{'pgn':0xd900},{'pgn':0xd800},{'pgn':0xd700},{'pgn':0xd400}] + parser = argparse.ArgumentParser(description='''\ + example: %(prog)s -d 0x21 0xe9 -p 0x15 + + will query a E9_15 Memory value ''' + ,epilog=title) - bus = j1939.Bus(channel='can0', bustype='socketcan', j1939_filters=filters, timeout=0.01) - node = j1939.Node(bus, j1939.NodeName(), [19]) - bus.connect(node) + parser.add_argument("-t", "--test", + action="store_true", default=False, + help="run the test cases and ignore input parameters") - node.start_address_claim() - node.claim_address(19) + parser.add_argument("-s", "--src", + default="0", + help="j1939 source address decimal or hex, default is 0") - time.sleep(5) - - pgn = j1939.PGN() - pgn.value = 0xd900 - logger.info("pgn: %s" % pgn) - aid = j1939.ArbitrationID(priority=7, pgn=pgn, source_address=0x19, destination_address=0x17) - logger.info("aid: %s" % aid) - if pgn.is_destination_specific: - logger.info("Destination Specific. dest=0x{:02x}".format(aid.destination_address)) + parser.add_argument("-d", "--dest", + default="0x17", + help="CAN destination, default is 0x17") + parser.add_argument("--speed", + default="250", + help="CAN bitrate {250|500}, default is 250") + parser.add_argument("--log", + default="WARNING", + help="Set LogLevel to DEBUG|INFO|(WARNING)|ERROR|CRITICAL for this app, j1939 and can") - data = [0x10, 0x13, 0x11, 0x00, 0x00, 0xe9, 0xff, 0xff] - pdu = j1939.PDU(timestamp=0.0, arbitration_id=aid, data=data, info_strings=None) - pdu.display_radix='hex' - logger.info("pdu: %s" % pdu) + parser.add_argument("-l", "--length", + default="4", + help="length in bytes (default: 4)") - logger.info("can Id: 0x{:08x}".format(pdu.arbitration_id.can_id)) - - bus.send(pdu) + parser.add_argument("-c", "--channel", + default="can0", + help="Memory object pointer offset to request in decimal or 0xHex") - if 0: - try: - for msg in bus: - msg.display_radix = 'hex' - logger.info(msg) - except KeyboardInterrupt: - bus.shutdown() - logger.info() - - time.sleep(2) - bus.shutdown() - + parser.add_argument("-b", "--bustype", + default="socketcan", + help="This depends on the channel for pcan use PCAN_USBBUS1") + + parser.add_argument("extension", + default=None, + help="Memory object extension prefix to request in decimal or 0xHex") + + parser.add_argument("pointer", + default=None, + help="Memory object pointer offset to request in decimal or 0xHex") + + + args = parser.parse_args() + + # assuming loglevel is bound to the string value obtained from the + # command line argument. Convert to upper case to allow the user to + # specify --log=DEBUG or --log=debug + numeric_level = getattr(logging, args.log.upper()) + if not isinstance(numeric_level, int): + raise ValueError('Invalid log level: {}'.format(args.log)) + logging.basicConfig(level=numeric_level) + + logger = logging.getLogger("j1939") + logger.setLevel(numeric_level) + + logger = logging.getLogger("can") + logger.setLevel(numeric_level) + + if (args.pointer==None or args.extension==None): + raise ValueError("pointer and extension are required!") + + source = int(args.src, 0) + dest = int(args.dest, 0) + ptr = int(args.pointer, 0) + length = int(args.length, 0) + ext = int(args.extension, 0) + speed = int(args.speed, 0) + channel = args.channel + bustype = args.bustype + logging.info ("get_mem_object_single(src=0x%02x, dest=0x%02x, pointer=0x%02x, extension/space=0x%02x, len=%d" % (source, dest, ptr, ext, length)) + + val = j1939.utils.get_mem_object(ptr, ext, length=length, src=source, dest=dest, channel=channel, bustype=bustype, speed=speed) + print("{}".format(val)) + out = '' + if isinstance(val, list): + for x in val: + out+=chr(x) + print(out) + +#(1648235377.698487) can0 1CD94100#0413000000F1FFFF +#(1648235377.712659) can0 18D80041#0411FFFFFFFFFFFF +#(1648235377.712660) can0 1CD70041#0400000000FFFFFF +#(1648235377.712661) can0 18D80041#0019FFFFFFFFFFFF \ No newline at end of file diff --git a/bin/j1939_mem_set.py b/bin/j1939_mem_set.py new file mode 100644 index 0000000..6cf2381 --- /dev/null +++ b/bin/j1939_mem_set.py @@ -0,0 +1,146 @@ +#!/usr/bin/python3 +# + +_name = "J1939 Memory-Object Writer" +__version__ = "1.0.2" +__date__ = "03/29/2021" +__exp__ = "()" # (Release Version) +title = "%s Version: %s %s %s" % (_name, __version__, __date__, __exp__) + +import sys +MIN_PYTHON = (3, 5) +if sys.version_info < MIN_PYTHON: + sys.exit("Python %s.%s or later is required.\n" % MIN_PYTHON) +import genkey +import j1939.utils + + + +if __name__ == "__main__": + import logging + import timeit + import argparse + + examples = """ +examples: + # NOTE: if the CAN speed is 500 add --speed 500 + # + # write the characters 'ERASE' to extension E9 ponter ED + $ python j1939_mem_set.py -d 0x17 0xe9 0xed ERASE + + # Illuminate 'N' Telltale, write a byte (1) to extension EA pointer 6B + $ python j1939_mem_set.py -d 0x17 0xea 0x6b 1 + + # extinguish 'N' Telltale, + $ python j1939_mem_set.py -d 0x17 0xea 0x6b 0 + + # Set B1/C4 Odometer to 200km (200=1km) + $ python j1939_mem_set.py --destination=0x17 --length=4 0xf1 0x00 20000 + + # Write 7 bytes of Backlight Settings. Note the array has to have no spaces + $ python j1939_mem_set.py -d 0x17 0xe9 0x01 [0x85,0x00,0x00,0x20,0x01,0x80,0x69] + # ptr off b0 b1 b2 b3 b4 b5 b6 + + +""" + + + #import logging + #import logging.handlers + + parser = argparse.ArgumentParser(description=title, formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) + + parser.add_argument("-l", "--length", default="1", help="number of bytes in the object (1-4) default=1") + parser.add_argument("-t", "--timeout", default="1000", help="ms before the request times out (default=1000 or 1s)") + parser.add_argument("-s", "--source", default="0", help="source address (0-254) default=0") + parser.add_argument("-d", "--destination", default="0x17", help="destination address (0-254) default=17") + parser.add_argument("-c", "--channel", default="can0", help="Generally can0 or PCAN_USBBUS1 on workstations or can1 on bbb/pbb targets") + parser.add_argument("-b", "--bustype", default="socketcan", help="channel bus type i.e. socketcan, pcan") + parser.add_argument("-r", "--robot", action="store_true", default=False, help="provide parsable result data to aid robot testing") + parser.add_argument("-v", "--verbose", action="store_true", default=False, help="Generate debugging output") + parser.add_argument( "--string", action="store_true", default=False, help="Treat an integer parameter as a string") + parser.add_argument( "--speed", default=250, help="CAN baudrate 250 or 500, (default 250)") + parser.add_argument("--log", + default="WARNING", + help="Set LogLevel to DEBUG|INFO|(WARNING)|ERROR|CRITICAL for this app, j1939 and can") + + parser.add_argument("extension", + default=None, + help="Memory object extension prefix to request in decimal or 0xhex") + + parser.add_argument("pointer", + default=None, + help="Memory object pointer offset to request in decimal or 0xhex") + + parser.add_argument('value', + default=None, + help="numeric or string value, if not a single byte, be sure to specify length.") + + args = parser.parse_args() + + # assuming loglevel is bound to the string value obtained from the + # command line argument. Convert to upper case to allow the user to + # specify --log=DEBUG or --log=debug + numeric_level = getattr(logging, args.log.upper()) + if not isinstance(numeric_level, int): + raise ValueError('Invalid log level: {}'.format(args.log)) + logging.basicConfig(level=numeric_level) + + logger = logging.getLogger("j1939") + logger.setLevel(numeric_level) + + logger = logging.getLogger("can") + logger.setLevel(numeric_level) + + if (args.pointer==None or args.extension==None or args.value==None): + raise ValueError("pointer, extension and value are required values!") + + length = int(args.length,0) + src = int(args.source,0) + dest = int(args.destination,0) + ext = int(args.extension,0) + ptr = int(args.pointer,0) + timeout = int(args.timeout,0) + value = args.value + + # + # Try first to pull out a numeric ir byte list argument, otherwise set it as a string. + # + try: + if args.string: + value = args.value + length = len(value) + print("Attepting to set extension/pointer 0x{:x}/0x{:x} to string {}, dlc={}".format(ext, ptr, value, len(value))) + elif args.value.startswith('['): + print ("processing array") + ar = args.value[1:-1] + print ("array = {}".format(ar)) + elements = ar.split(',') + print ("elements = {}".format(elements)) + value = [int(s,0) for s in elements] + print ("elements = {}".format(elements)) + length = len(value) + print("Attepting to set extension/pointer 0x{:x}/0x{:x} to array {}, dlc={}".format(ext, ptr, value, len(value))) + else: + value = int(args.value,0) + print("Attepting to set extension/pointer 0x{:x}/0x{:x} to integer {}, dlc={}".format(ext, ptr, value, length)) + + except ValueError: + if length < len(value): + length = len(value) + + #value = hex(int(args.value,0)) + + # queries a couple objects but setting up the full stack and bus for + # each takes a long time. + start = timeit.default_timer() + val = j1939.utils.set_mem_object(ptr, ext, value, speed=args.speed, channel=args.channel, bustype=args.bustype, length=length, src=src, dest=dest, timeout=timeout) + #set_mem_object_single(length=1, src=0, dest=0x17, pointer=0x66, extension=0xea, value=127) + print("elapsed = %s s" % (timeit.default_timer() - start)) + print("return val = {}".format(int(val!=1))) + if args.robot: + if val!=1: + print("RESULT=FAIL") + else: + print("RESULT=SUCCESS") + sys.exit(val!=1) diff --git a/bin/j1939_request_pgn.py b/bin/j1939_request_pgn.py new file mode 100644 index 0000000..bc3cb97 --- /dev/null +++ b/bin/j1939_request_pgn.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# +from __future__ import print_function + +_name = "j1939_request_pgn" +__version__ = "1.0.0" +__date__ = "12/21/2017" +__exp__ = "(expirimental)" # (Release Version) +title = "%s Version: %s %s %s" % (_name, __version__, __date__, __exp__) + +import j1939.utils + + + +if __name__ == "__main__": + + import traceback + import timeit + import time + import argparse + import logging + import textwrap + + def getStringValAsInt(s): + if s.startswith("0x"): + return int(s[2:],base=16) + else: + return int(s, base=10) + + + logger = logging.getLogger("j1939") + ch = logging.StreamHandler() + ch.setLevel(logging.WARNING) + logger.addHandler(ch) + + parser = argparse.ArgumentParser(description='''\ + example: %(prog)s -d 0x21 65223 + + will request a specific PGN 65223 from dest ''' + ,epilog=title) + + parser.add_argument("-s", "--src", + default="0", + help="j1939 source address decimal or hex, default is 0") + + parser.add_argument("-d", "--dest", + default="0x17", + help="CAN destination, default is 0x17") + + parser.add_argument("pgn", + default=None, + help="pgn to request in decimal or 0xHex") + + args = parser.parse_args() + + + source = getStringValAsInt(args.src) + dest = getStringValAsInt(args.dest) + pgn = getStringValAsInt(args.pgn) + + print ("j1939.utils.request_pgn(pgn=0x%04x (%d), src=0x%02x, dest=0x%02x)" % (pgn, pgn, source, dest)) + + val = j1939.utils.request_pgn(pgn, length=4, src=source, dest=dest) + + print("returned PGN = %s" % val) + diff --git a/bin/j1939_send_pgn.py b/bin/j1939_send_pgn.py new file mode 100644 index 0000000..ade24d7 --- /dev/null +++ b/bin/j1939_send_pgn.py @@ -0,0 +1,89 @@ +#!/usr/bin/python +# +from __future__ import print_function + +_name = "" +__version__ = "1.0.0" +__date__ = "12/21/2017" +__exp__ = "(expirimental)" # (Release Version) +title = "%s Version: %s %s %s" % (_name, __version__, __date__, __exp__) + +import j1939.utils + + +if __name__ == "__main__": + + import traceback + import timeit + import time + import argparse + import logging + import textwrap + + def getStringValAsInt(s): + if s.startswith("0x"): + return int(s[2:],base=16) + else: + temp = int(s, base=10) + # 4 bit numbers need to not have a preceding 0 + #if temp < 0xf0: + # temp = temp << 8 + return temp + + + logger = logging.getLogger("j1939") + ch = logging.StreamHandler() + ch.setLevel(logging.WARNING) + logger.addHandler(ch) + + parser = argparse.ArgumentParser(description='''\ + example: %(prog)s -d 0x21 65223 + + will request a specific PGN 65223 from dest ''' + ,epilog=title) + + parser.add_argument("-s", "--src", + default="0", + help="j1939 source address decimal or hex, default is 0") + + parser.add_argument("-d", "--dest", + default="0x17", + help="CAN destination, default is 0x17") + + parser.add_argument("pgn", + default=None, + help="pgn to request in decimal or 0xHex") + parser.add_argument("data", + nargs='*', + default=[], + help='''Data to be sent with pgn. + Accepts: int/hex values or a single string''') + args = parser.parse_args() + + + source = getStringValAsInt(args.src) + dest = getStringValAsInt(args.dest) + pgn = getStringValAsInt(args.pgn) + data = args.data + print(data) + if len(data) == 1: + try: + value = getStringValAsInt(data[0]) + data = [value] + except ValueError: + data = [ord(c) for c in data[0]] + print(data) + elif len(data) > 1: + data = [getStringValAsInt(x) for x in data] + + if len(data) > 8: + print("Unable to send {} buffer of size: {}".format(data, len(data))) + exit() + + + print ("Sending PGN: (pgn=0x%04x (%d), src=0x%02x, dest=0x%02x)" % (pgn, pgn, source, dest)) + + val = j1939.utils.send_pgn(pgn, data, length=len(data), src=source, dest=dest) + + print("returned PGN = %s" % val) + diff --git a/examples/get_ecu_block.py b/examples/get_ecu_block.py new file mode 100644 index 0000000..c107d16 --- /dev/null +++ b/examples/get_ecu_block.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# +from __future__ import print_function + +_name = "get_ecu_block" +__version__ = "1.1.0" +__date__ = "8/2/2018" +__exp__ = "(expirimental)" # (Release Version) +title = "%s Version: %s %s %s" % (_name, __version__, __date__, __exp__) + +import argparse +import j1939.utils + +parser = argparse.ArgumentParser(description='''\ + example: %(prog)s -d 0x17 + + will request ecu block from destination ''' + ,epilog=title) + +parser.add_argument("-s", "--src", + default="0xff", + help="j1939 source address decimal or hex, default is 0") + +parser.add_argument("-d", "--dest", + default="0x17", + help="CAN destination, default is 0x17") + +parser.add_argument("-t", "--timeout", + default="10", + help="larger numbers give the dest more time to respond") + +args = parser.parse_args() + + +source = int(args.src, 0) +dest = int(args.dest, 0) +timeout = int(args.timeout, 0) + +#expects a BAM back pretty +val = j1939.utils.request_pgn(0xfeda, src=source, dest=dest, timeout=timeout) + +fieldCount = val[0] +print ("%d elements in list" % fieldCount) + +res="" +for c in val[1:]: + res += chr(c) + +res2=res.split('*') +print (res2) + +print("Destination Address: %d (0x%x)" % (23, 23)) +print("Number of ID items: %d (0x%x)" % (fieldCount, fieldCount)) +print("Model Number: %s " % (res2[0])) +print("Vendor Part Number: %s" % (res2[1])) +print("Hardware Serial Number: %s" % (res2[2])) +print("VIN: %s" % (res2[3])) +print("Hardware Part Number: %s" % (res2[4])) +print("ECU Part Number: %s" % (res2[5])) +print("Program Version: %s" % (res2[6])) +print("Software Version Number: %s" % (res2[7])) +print("Software Part Number: %s" % (res2[8])) +print("Checksum: 0x%s" % (res2[9])) + + + diff --git a/examples/get_modem_block.py b/examples/get_modem_block.py new file mode 100644 index 0000000..ee79d98 --- /dev/null +++ b/examples/get_modem_block.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# +from __future__ import print_function + +_name = "get_modem_block" +__version__ = "1.0.0" +__date__ = "2/18/2019" +__exp__ = "(experimental)" # (Release Version) +title = "%s Version: %s %s %s" % (_name, __version__, __date__, __exp__) + +import argparse +import j1939.utils + +parser = argparse.ArgumentParser(description='''\ + example: %(prog)s -d 0x17 + + will request ecu block from destination ''' + ,epilog=title) + +parser.add_argument("-s", "--src", + default="0xff", + help="j1939 source address decimal or hex, default is 0") + +parser.add_argument("-d", "--dest", + default="0x41", + help="CAN destination, default is 0x41") + +parser.add_argument("-t", "--timeout", + default="20", + help="larger numbers give the dest more time to respond") + +args = parser.parse_args() + + +source = int(args.src, 0) +dest = int(args.dest, 0) +timeout = int(args.timeout, 0) + +#expects a BAM back pretty +val = j1939.utils.request_pgn(0xffb2, src=source, dest=dest, timeout=timeout) + +fieldCount = val[0] +print ("%d elements in list" % fieldCount) + +res="" +for c in val[1:]: + res += chr(c) + +res2=res.split('*') +#print (res2) + +print("Destination Address: %d (0x%x)" % (dest, dest)) +print("Number of ID items: %d (0x%x)" % (fieldCount, fieldCount)) +print("Modem IMEI: %s" % (res2[0])) +print("Modem ICCID: %s" % (res2[1])) + + + diff --git a/examples/get_modem_status.py b/examples/get_modem_status.py new file mode 100644 index 0000000..a3dff61 --- /dev/null +++ b/examples/get_modem_status.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# +from __future__ import print_function + +_name = "get_modem_block" +__version__ = "1.0.0" +__date__ = "2/18/2019" +__exp__ = "(experimental)" # (Release Version) +title = "%s Version: %s %s %s" % (_name, __version__, __date__, __exp__) + +import argparse +import j1939.utils + +parser = argparse.ArgumentParser(description='''\ + example: %(prog)s -d 0x17 + + will request ecu block from destination ''' + ,epilog=title) + +parser.add_argument("-s", "--src", + default="0xff", + help="j1939 source address decimal or hex, default is 0") + +parser.add_argument("-d", "--dest", + default="0x41", + help="CAN destination, default is 0x41") + +parser.add_argument("-t", "--timeout", + default="20", + help="larger numbers give the dest more time to respond") + +args = parser.parse_args() + + +source = int(args.src, 0) +dest = int(args.dest, 0) +timeout = int(args.timeout, 0) + +#expects a BAM back pretty +val = j1939.utils.request_pgn(0xffb8, src=source, dest=dest, timeout=timeout) + +#print(val) + +print("Destination Address: %d (0x%x)" % (dest, dest)) +print("Modem ping status: %d" % (val[0] & 0xF)) +print("Modem registration status: %d" % ((val[0] & 0xF0) >> 4)) +print("Modem signal strength: %d" % (val[1] & 0x7)) +print("Modem responding: %s" % ("true" if (((val[1] & 0x18) >> 3) == 1) else "false")) +if val[2] == 255: + print("Modem RSRQ: Not available") +else: + print("Modem RSRQ: %0.1f dB" % (val[2]/2 - 20.0)) + +if val[3] == 255: + print("Modem RSRP: Not available") +else: + print("Modem RSRP: %0.1f dBm" % (val[3] - 140.0)) + + + diff --git a/examples/j1939_nodes.py b/examples/j1939_nodes.py index 5f0a32b..5a41512 100644 --- a/examples/j1939_nodes.py +++ b/examples/j1939_nodes.py @@ -1,14 +1,20 @@ -from __future__ import print_function +# +# This example is an attempt to operate two address endpoints concurrently. +# +# Currently it's not tested and I suspect it's not handling the address claims correctly +# among other things +# + -from time import sleep +from __future__ import print_function import can import j1939 import logging -lLevel = logging.DEBUG +lLevel = logging.WARNING -logger = logging.getLogger() +logger = logging.getLogger("j1939") logger.setLevel(lLevel) ch = logging.StreamHandler() fh = logging.FileHandler('/tmp/j1939_nodes.log') @@ -52,18 +58,8 @@ def send_j1939(): msg = j1939.PDU(arbitration_id=arbitration_id, data=[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80]) - sleep(0.5) node1.start_address_claim() - sleep(0.5) - - if 0: - try: - bus.send(msg) - logging.debug("Message sent on {}".format(bus.channel_info)) - except can.CanError: - logging.debug("Message NOT sent") - sleep(1) bus.flush_tx_buffer() bus.shutdown() diff --git a/examples/simple-17bit-send.py b/examples/simple-17bit-send.py new file mode 100755 index 0000000..c902641 --- /dev/null +++ b/examples/simple-17bit-send.py @@ -0,0 +1,44 @@ +import time +import j1939 + +if __name__ == "__main__": + + # code to broadcast a DM1 message at 1 Hz + # 18FECAFE#FFFF00000000FFFF + + channel = 'can0' + bustype = 'socketcan' + sourceaddr = 0xFE + destaddr = 0xFF + + + bus = j1939.Bus(channel=channel, bustype=bustype, timeout=0.01, broadcast=False) + + data = [0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF] + pgn = j1939.PGN() + pgn2 = j1939.PGN() + pgn3 = j1939.PGN() + #pgn.value = 0xFECA # DM1 + pgn.value = 0xFF17 # DM1 + pgn2.value = 0x1FF17 # DM1 + pgn3.value = 0x2FF17 # DM1 + print("pgn.values: {}/{}".format(pgn.value, pgn2.value)) + aid = j1939.ArbitrationID(pgn=pgn, source_address=sourceaddr, destination_address=destaddr) + aid2 = j1939.ArbitrationID(pgn=pgn2, source_address=sourceaddr, destination_address=destaddr) + aid3 = j1939.ArbitrationID(pgn=pgn3, source_address=sourceaddr, destination_address=destaddr) + print("aid: {}".format(aid)) + pdu = j1939.PDU(timestamp=0.0, arbitration_id=aid, data=data, info_strings=None) + pdu2 = j1939.PDU(timestamp=0.0, arbitration_id=aid2, data=data, info_strings=None) + pdu3 = j1939.PDU(timestamp=0.0, arbitration_id=aid3, data=data, info_strings=None) + print("pdu: {}".format(pdu)) + + while True: + print("pre send\n {}\n {}\n {}".format(pdu, pdu2, pdu3)) + bus.send(pdu) + bus.send(pdu2) + bus.send(pdu3) + time.sleep(1) + + + + diff --git a/examples/simple-dm1-send.py b/examples/simple-dm1-send.py new file mode 100755 index 0000000..69df080 --- /dev/null +++ b/examples/simple-dm1-send.py @@ -0,0 +1,29 @@ +import time +import j1939 + +if __name__ == "__main__": + + # code to broadcast a DM1 message at 1 Hz + # 18FECAFE#FFFF00000000FFFF + + channel = 'can0' + bustype = 'socketcan' + sourceaddr = 0xFE + destaddr = 0xFF + + + bus = j1939.Bus(channel=channel, bustype=bustype, timeout=0.01, broadcast=False) + + data = [0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF] + pgn = j1939.PGN() + pgn.value = 0xFECA # DM1 + aid = j1939.ArbitrationID(pgn=pgn, source_address=sourceaddr, destination_address=destaddr) + pdu = j1939.PDU(timestamp=0.0, arbitration_id=aid, data=data, info_strings=None) + + while True: + bus.send(pdu) + time.sleep(1) + + + + diff --git a/j1939/__init__.py b/j1939/__init__.py index bd1fb7e..d2d8c29 100644 --- a/j1939/__init__.py +++ b/j1939/__init__.py @@ -10,40 +10,75 @@ import threading import logging +import logging.handlers import pprint - +import time +import tempfile +import os try: from queue import Queue, Empty except ImportError: from Queue import Queue, Empty -import time import copy # By this stage the can.rc should have been set up +from can import CanError from can import Message -from can.interfaces.interface import Bus as RawCanBus -from can.notifier import Notifier as canNotifier +from can import set_logging_level as can_set_logging_level +from can.interface import Bus as RawCanBus +from can.listener import Listener as canListener from can.bus import BusABC # Import our new message type from j1939.pdu import PDU from j1939.pgn import PGN from j1939.constants import * -from j1939.notifier import Notifier +from j1939.notifier import Notifier, CanNotifier as canNotifier from j1939.node import Node from j1939.nodename import NodeName from j1939.arbitrationid import ArbitrationID +from j1939.utils import * +lLevel = logging.WARNING -logger = logging.getLogger(__name__) -#logger.setLevel(logging.DEBUG) +logger = logging.getLogger("j1939") +logger.setLevel(lLevel) +logger.setLevel(lLevel) +ch = logging.StreamHandler() +ch.setLevel(lLevel) +chformatter = logging.Formatter('%(name)25s | %(threadName)10s | %(levelname)5s | %(message)s') +ch.setFormatter(chformatter) +#logger.addHandler(ch) +__version__ = "1.0.0" -class Bus(BusABC): +if 1: + fileHandler = logging.handlers.RotatingFileHandler(os.path.join(tempfile.gettempdir(), \ + 'j1939.log'), \ + maxBytes = (1024*1024*20), \ + backupCount = 4) + fileHandler.setFormatter(chformatter) + fileHandler.setLevel(lLevel) + logger.addHandler(fileHandler) + + can_set_logging_level('debug') + +class j1939Listner(canListener): + + def __init__(self, handler): + self.handler = handler + + def on_message_received(self, msg): + self.handler(msg) + def stop(self): + pass + + +class Bus(BusABC): """ A CAN Bus that implements the J1939 Protocol. @@ -63,24 +98,31 @@ def __init__(self, pdu_type=PDU, broadcast=True, *args, **kwargs): logger.debug("Creating a new j1939 bus") #self.rx_can_message_queue = Queue() - self.queue = Queue() - self.node_queue_list = [] # Start with nothing + self.queue = Queue() + self.node_queue_list = [] # Start with nothing - super(Bus, self).__init__() + super(Bus, self).__init__(kwargs.get('channel'), kwargs.get('can_filters')) self._pdu_type = pdu_type - self.timeout=1 + self.timeout = 1 self._long_message_throttler = threading.Thread(target=self._throttler_function) - #self._long_message_throttler.daemon = True + self._long_message_throttler.daemon = True self._incomplete_received_pdus = {} self._incomplete_received_pdu_lengths = {} self._incomplete_transmitted_pdus = {} self._long_message_segment_queue = Queue(0) + self._key_generation_fcn = None + self._ignore_can_send_error = False + + self._key_generation_fcn = kwargs.get('keygen') + logger.debug("----PI01d: self._key_generation_fcn={}".format(self._key_generation_fcn)) + + + self._ignore_can_send_error = kwargs.get('ignoreCanSendError') if broadcast: - self.node_queue_list = [(None, self)] # Start with default logger Queue - # which will receive everything + self.node_queue_list = [(None, self)] # Start with default logger Queue which will receive everything # Convert J1939 filters into Raw Can filters @@ -112,71 +154,75 @@ def __init__(self, pdu_type=PDU, broadcast=True, *args, **kwargs): if 'timeout' in kwargs and kwargs['timeout'] is not None: if isinstance(kwargs['timeout'], (int, float)): - self.timeout=kwargs['timeout'] + self.timeout = kwargs['timeout'] else: raise ValueError("Bad timeout type") logger.debug("Creating a new can bus") self.can_bus = RawCanBus(*args, **kwargs) - self.can_notifier = canNotifier(self.can_bus, [self.notification], timeout=self.timeout) - ###########self.j1939_notifier = Notifier(self, []) + + canListener = j1939Listner(self.notification) + self.can_notifier = canNotifier(self.can_bus, [canListener], timeout=self.timeout) self._long_message_throttler.start() + def notification(self, inboundMessage): #self.rx_can_message_queue.put(inboundMessage) + if self.can_notifier._running is False: + logger.info('{}: Aborting message {} bus is not running'.format(inspect.stack()[0][3], inboundMessage)) + # Should I return or throw exception here. if isinstance(inboundMessage, Message): - logger.info('\n\nnotification: Got a Message from CAN: %s' % inboundMessage) - if inboundMessage.id_type: + logger.info('\n\n{}: Got a Message from CAN: {}'.format(inspect.stack()[0][3],inboundMessage)) + if inboundMessage.is_extended_id: # Extended ID # Only J1939 messages (i.e. 29-bit IDs) should go further than this point. # Non-J1939 systems can co-exist with J1939 systems, but J1939 doesn't care # about the content of their messages. - logger.info('notification: Message is j1939 msg') + logger.info('{}: Message is j1939 msg'.format(inspect.stack()[0][3])) # - # Need to determine if it's a broadcase message or + # Need to determine if it's a broadcast message or # limit to listening nodes only # arbitration_id = ArbitrationID() arbitration_id.can_id = inboundMessage.arbitration_id - logger.debug('notification: ArbitrationID = %s' % (arbitration_id)) + logger.info("{}: ArbitrationID = {}, inboundMessage.arbitration_id: 0x{:08x}".format(inspect.stack()[0][3],arbitration_id, inboundMessage.arbitration_id)) for (node, l_notifier) in self.node_queue_list: - logger.info("notification: node=%s" % (node)) - logger.info(" notifier=%s" % (l_notifier)) - logger.info(" arbitration_id.pgn=%s" % (arbitration_id.pgn)) - logger.info(" destination_address=%s" % (arbitration_id.destination_address)) + logger.debug("notification: node=%s" % (node)) + logger.debug(" notifier=%s" % (l_notifier)) + logger.debug(" arbitration_id.pgn=%s" % (arbitration_id.pgn)) + logger.debug(" destination_address=%s" % (arbitration_id.destination_address)) # redirect the AC stuff to the node processors. the rest can go # to the main queue. if node and (arbitration_id.pgn in [PGN_AC_ADDRESS_CLAIMED, PGN_AC_COMMANDED_ADDRESS, PGN_REQUEST_FOR_PGN]): - logger.info("notification: sending to notifier queue") + logger.info("{}: sending to notifier queue".format(inspect.stack()[0][3])) # send the PDU to the node processor. l_notifier.queue.put(inboundMessage) # if node has the destination address, do something with the PDU - # elif node and (arbitration_id.destination_address in node.address_list): - logger.info("notification: sending to general queue") + logger.info("{}: sending to process_incoming_message".format(inspect.stack()[0][3])) rx_pdu = self._process_incoming_message(inboundMessage) - self.queue.put(rx_pdu) - - elif node and (arbitration_id.destination_address == None): - logger.info("notification: sending broadcast to general queue") + if rx_pdu: + logger.info("WP02: notification: sent to general queue: %s QQ=%s" % (rx_pdu, self.queue)) + self.queue.put(rx_pdu) + elif node and (arbitration_id.destination_address is None): + logger.info("{}: sending broadcast to general queue".format(inspect.stack()[0][3])) rx_pdu = self._process_incoming_message(inboundMessage) + logger.info("WP01: notification: sent broadcast to general queue: %s QQ=%s" % (rx_pdu, self.queue)) self.queue.put(rx_pdu) - - elif node==None: + elif node is None: # always send the message to the logging queue - logger.info("notification: sending to general queue") + logger.info("{}: sending to general queue".format(inspect.stack()[0][3])) rx_pdu = self._process_incoming_message(inboundMessage) + logger.info("WP03: notification: sent pdu [%s] to general queue" % rx_pdu) self.queue.put(rx_pdu) - else: - logger.info("notification: pdu dropped: %s\n\n" % inboundMessage) - + logger.info("WP04: notification: pdu dropped: %s\n\n" % inboundMessage) else: logger.info("Received non J1939 message (ignoring)") @@ -191,32 +237,33 @@ def connect(self, node): notifier = Notifier(Queue(), node.on_message_received, timeout=None) self.node_queue_list.append((node, notifier)) - def recv(self, timeout=None): #logger.debug("Waiting for new message") #logger.debug("Timeout is {}".format(timeout)) + logger.debug('J1939 Bus recv(), waiting on QQ=%s with timeout %s' % (self.queue, timeout)) try: #m = self.rx_can_message_queue.get(timeout=timeout) rx_pdu = self.queue.get(timeout=timeout) + logger.info('J1939 Bus recv() successful QQ=%s, pdu:%s' % (self.queue, rx_pdu)) return rx_pdu + except Empty: + logger.debug('J1939 Bus recv() timed out' % ()) return None # TODO: Decide what to do with CAN errors - if None and m.is_error_frame: - logger.warning("Appears we got an error frame!") - - rx_error = CANError(timestamp=m.timestamp) - if rx_error is not None: - logger.info('Sending error "%s" to registered listeners.' % rx_error) - for listener in self.listeners: - if hasattr(listener, 'on_error_received'): - listener.on_error_received(rx_error) - - - - def send(self, msg): - logger.info("j1939.send: msg=%s" % msg) + # if m.is_error_frame: + # logger.warning("Appears we got an error frame!") + # + # rx_error = CANError(timestamp=m.timestamp) + # if rx_error is not None: + # logger.info('Sending error "%s" to registered listeners.' % rx_error) + # for listener in self.listeners: + # if hasattr(listener, 'on_error_received'): + # listener.on_error_received(rx_error) + + def send(self, msg, timeout=None): + logger.info("j1939.send: msg={}".format(msg)) messages = [] if len(msg.data) > 8: logger.info("j1939.send: message is > than 8 bytes") @@ -225,41 +272,66 @@ def send(self, msg): pdu = copy.deepcopy(msg) pdu.data = bytearray(pdu.data) + logger.info("j1939.send: Copied msg = {}".format(pdu)) pdu_length_lsb, pdu_length_msb = divmod(len(pdu.data), 256) while len(pdu.data) % 7 != 0: pdu.data += b'\xFF' + logger.info("j1939.send: padded msg (mod 7) = %s" % pdu) + logger.info("MIL8:---------------------") + + # + # segment the longer message into 7 byte segments. We need to prefix each + # data[0] with a sequence number for the transfer + # for i, segment in enumerate(pdu.data_segments(segment_length=7)): arbitration_id = copy.deepcopy(pdu.arbitration_id) arbitration_id.pgn.value = PGN_TP_DATA_TRANSFER + + logger.info("MIL8: j1939.send: i=%d, pdu.arbitration_id.pgn.is_destination_specific=%d, data=%s" % + (i,pdu.arbitration_id.pgn.is_destination_specific, segment)) + if pdu.arbitration_id.pgn.is_destination_specific and \ pdu.arbitration_id.destination_address != DESTINATION_ADDRESS_GLOBAL: + arbitration_id.pgn.pdu_specific = pdu.arbitration_id.pgn.pdu_specific else: arbitration_id.pgn.pdu_specific = DESTINATION_ADDRESS_GLOBAL + arbitration_id.destination_address = DESTINATION_ADDRESS_GLOBAL + logger.info("MIL8: j1939.send: segment=%d, arb = %s" % (i, arbitration_id)) message = Message(arbitration_id=arbitration_id.can_id, - extended_id=True, + is_extended_id=True, dlc=(len(segment) + 1), data=(bytearray([i + 1]) + segment)) messages.append(message) + # + # At this point we have the queued messages sequenced in 'messages' + # + logger.info("MIL8: j1939.send: is_destination_specific={}, destAddr={}".format(pdu.arbitration_id.pgn.is_destination_specific, pdu.arbitration_id.destination_address)) + logger.info("MIL8: j1939.send: messages=%s" % messages) + if pdu.arbitration_id.pgn.is_destination_specific and \ pdu.arbitration_id.destination_address != DESTINATION_ADDRESS_GLOBAL: + destination_address = pdu.arbitration_id.pgn.pdu_specific + if pdu.arbitration_id.source_address in self._incomplete_transmitted_pdus: if destination_address in self._incomplete_transmitted_pdus[pdu.arbitration_id.source_address]: logger.warning("Duplicate transmission of PDU:\n{}".format(pdu)) else: self._incomplete_transmitted_pdus[pdu.arbitration_id.source_address] = {} + + # append the messages to the 'incomplete' list self._incomplete_transmitted_pdus[pdu.arbitration_id.source_address][destination_address] = messages + else: destination_address = DESTINATION_ADDRESS_GLOBAL - logger.warning("rts arbitration id: src=%s, dest=%s" % (pdu.source, pdu.arbitration_id.destination_address)) - rts_arbitration_id = ArbitrationID(source_address=pdu.source, destination_address=pdu.arbitration_id.destination_address) - rts_arbitration_id.pgn.value = PGN_TP_CONNECTION_MANAGEMENT + logger.debug("MIL8: rts arbitration id: src=%s, dest=%s" % (pdu.source, destination_address)) + rts_arbitration_id = ArbitrationID(pgn=PGN_TP_CONNECTION_MANAGEMENT, source_address=pdu.source, destination_address=destination_address) rts_arbitration_id.pgn.pdu_specific = pdu.arbitration_id.pgn.pdu_specific temp_pgn = copy.deepcopy(pdu.arbitration_id.pgn) @@ -273,7 +345,8 @@ def send(self, msg): if pdu.arbitration_id.pgn.is_destination_specific and \ pdu.arbitration_id.destination_address != DESTINATION_ADDRESS_GLOBAL: # send request to send - rts_msg = Message(extended_id=True, + logger.debug("MIL8: rts to specific dest: src=%s, dest=%s" % (pdu.source, destination_address)) + rts_msg = Message(is_extended_id=True, arbitration_id=rts_arbitration_id.can_id, data=[CM_MSG_TYPE_RTS, pdu_length_msb, @@ -284,11 +357,19 @@ def send(self, msg): pgn_middle, pgn_msb], dlc=8) - self.can_bus.send(rts_msg) + try: + logger.info("MIL08: j1939.send: sending TP.RTS to %s: %s" % (destination_address, rts_msg)) + self.can_bus.send(rts_msg) + except CanError: + if self._ignore_can_send_error: + pass + raise else: rts_arbitration_id.pgn.pdu_specific = DESTINATION_ADDRESS_GLOBAL - bam_msg = Message(extended_id=True, - arbitration_id=rts_arbitration_id.can_id, + rts_arbitration_id.destination_address = DESTINATION_ADDRESS_GLOBAL + logger.debug("MIL8: rts to Global dest: src=%s, dest=%s" % (pdu.source, destination_address)) + bam_msg = Message(is_extended_id=True, + arbitration_id=rts_arbitration_id.can_id | pdu.source, data=[CM_MSG_TYPE_BAM, pdu_length_msb, pdu_length_lsb, len(messages), @@ -297,59 +378,115 @@ def send(self, msg): pgn_middle, pgn_msb], dlc=8) + bam_msg.destination_address = DESTINATION_ADDRESS_GLOBAL + # bam_msg.arbitration_id.destination_address = DESTINATION_ADDRESS_GLOBAL # send BAM - self.can_bus.send(bam_msg) + try: + logger.info("j1939.send: sending TP.BAM to %s: %s" % (destination_address, bam_msg)) + self.can_bus.send(bam_msg) + time.sleep(0.05) + except CanError: + if self._ignore_can_send_error: + pass + raise for message in messages: # send data messages - no flow control, so no need to wait # for receiving devices to acknowledge + logger.info("j1939.send: queue TP.BAM data to %s: %s" % (destination_address, message)) self._long_message_segment_queue.put_nowait(message) else: msg.display_radix = 'hex' - logger.info("j1939.send: calling can_bus_send: j1939-msg: %s" % (msg)) - logger.info("j1939.send: calling can_bus_send: can_id %x" % (msg.arbitration_id.can_id)) + logger.debug("j1939.send: calling can_bus_send: j1939-msg: {}, arb-id: {:08x}".format(msg, msg.arbitration_id.can_id)) can_message = Message(arbitration_id=msg.arbitration_id.can_id, - extended_id=True, + is_extended_id=True, dlc=len(msg.data), data=msg.data) - logger.info("j1939.send: calling can_bus_send: can-msg: %s" % can_message) - self.can_bus.send(can_message) - + logger.debug("j1939.send: calling can_bus_send: can-msg: {}".format(can_message)) + try: + self.can_bus.send(can_message) + except CanError: + if self._ignore_can_send_error: + pass + raise def shutdown(self): - self.can_notifier.running.clear() + self.can_notifier._running = False self.can_bus.shutdown() #self.j1939_notifier.running.clear() super(Bus, self).shutdown() + def _send_key_response(self, pdu): + logger.info("PI04: _send_key_response src=%d, pdu=%s" % (pdu.source, pdu)) + src = pdu.destination + dest = pdu.source + logger.info("PI05: new PDU, src=%d, dest=%d" % (src, dest)) + pdu.destination = dest + logger.info("PI05: new PDU.dest = %d" % (pdu.destination)) + pdu.source = src + logger.info("PI06: newPDU = %s" % (pdu)) + + logger.info("PI04: _send_key_response src/dest flipped pdu=%s" % (pdu)) + assert(pdu.data[0] == 4) # only support long key for now + + data = pdu.data + assert(len(data) == 8) + + seed = (data[5] << 24) + (data[4] << 16) + (data[3] << 8) + data[2] + if self._key_generation_fcn is None: + return None + key = self._key_generation_fcn(seed) + + logger.info("PI03: _send_key_response Seed: 0x%08x yields key: 0x%08x" % (seed, key)) + + data[5] = (key >> 24) & 0xff + data[4] = (key >> 16) & 0xff + data[3] = (key >> 8) & 0xff + data[2] = (key) & 0xff + data[1] = 1 + + pdu.data = data + + self.send(pdu) + + return None + def _process_incoming_message(self, msg): - logger.debug("Processing incoming message: \n %s" % (msg)) + logger.info("PI01: Processing incoming message: instance={}, msg= {}".format(self, msg)) arbitration_id = ArbitrationID() arbitration_id.can_id = msg.arbitration_id if arbitration_id.pgn.is_destination_specific: arbitration_id.pgn.value -= arbitration_id.pgn.pdu_specific + pdu = self._pdu_type(timestamp=msg.timestamp, data=msg.data, info_strings=[]) pdu.arbitration_id.can_id = msg.arbitration_id pdu.info_strings = [] - logging.debug(pdu) + pdu.radix = 16 + + logger.debug("PI02a: arbitration_id.pgn.value = 0x{:04x} ({})".format(arbitration_id.pgn.value, arbitration_id.pgn.value)) + logger.debug("PI02b: PGN_TP_SEED_REQUEST = {}".format(PGN_TP_SEED_REQUEST)) + + logger.debug("PI02c: self._key_generation_fcn = {}".format(self._key_generation_fcn)) if arbitration_id.pgn.value == PGN_TP_CONNECTION_MANAGEMENT: - logger.debug("PGN_TP_CONNECTION_MANAGEMENT") + logger.info("PGN_TP_CONNECTION_MANAGEMENT") retval = self._connection_management_handler(pdu) elif arbitration_id.pgn.value == PGN_TP_DATA_TRANSFER: - logger.debug("PGN_TP_DATA_TRANSFER") + logger.info("PGN_TP_DATA_TRANSFER") retval = self._data_transfer_handler(pdu) + elif (arbitration_id.pgn.value == PGN_TP_SEED_REQUEST) and (self._key_generation_fcn is not None): + logger.info("PGN_TP_SEED_REQUEST") + retval = self._send_key_response(pdu) else: - logger.debug("PGN_PDU") + logger.info("PGN_PDU generic") retval = pdu - logger.debug("\n") - logging.debug("_process_incoming_message: returning %s" % (retval)) + logger.info("_process_incoming_message: returning %s" % (retval)) return retval def _connection_management_handler(self, msg): - logger.debug("_connection_management_handler: %s" % (msg)) + logger.debug("MP00: _connection_management_handler: %s, cmd=%s" % (msg, msg.data[0])) if len(msg.data) == 0: msg.info_strings.append("Invalid connection management message - no data bytes") return msg @@ -395,7 +532,7 @@ def _data_transfer_handler(self, msg): # Find a Node object so we can search its list of known node addresses for this node # so we can find if we are responsible for sending the EOM ACK message - # TODO: Was self.j1939_notifier.listeners + # TODO: Was self.j1939_notifier.listeners send_ack = any(True for (_listener, l_notifier) in self.node_queue_list if isinstance(_listener, Node) and @@ -424,7 +561,7 @@ def _data_transfer_handler(self, msg): ()) div, mod = divmod(total_length, 256) can_message = Message(arbitration_id=arbitration_id.can_id, - extended_id=True, + is_extended_id=True, dlc=8, data=[CM_MSG_TYPE_EOM_ACK, mod, # total_length % 256, @@ -434,7 +571,12 @@ def _data_transfer_handler(self, msg): _pgn_lsb, _pgn_middle, pgn_msb]) - self.can_bus.send(can_message) + try: + self.can_bus.send(can_message) + except CanError: + if self._ignore_can_send_error: + pass + raise logger.debug("_data_transfer_handler: returning %s" % (msg)) return self._process_eom_ack(msg) @@ -494,10 +636,15 @@ def _process_rts(self, msg): #for _listener in self.can_notifier.listeners: for (_listener, l_notifier) in self.node_queue_list: + logger.debug("MIL2: _listner/l_notifier = %s/%s" % (_listener, l_notifier)) if isinstance(_listener, Node): logger.debug("6, dest=0x%x" % (msg.arbitration_id.source_address)) # find a Node object so we can search its list of known node addresses # for this node - if we find it we are responsible for sending the CTS message + logger.debug("MIL3: Node: %s" % (Node)) + logger.debug("MIL3: _listener.address: %s" % (_listener.address)) + logger.debug("MIL3: msg.arbitration_id.pgn.pdu_specific: %s" % (msg.arbitration_id.pgn.pdu_specific)) + logger.debug("MIL3: _listener.address_list: %s" % (_listener.address_list)) if _listener.address == msg.arbitration_id.pgn.pdu_specific or \ msg.arbitration_id.pgn.pdu_specific in _listener.address_list: _cts_arbitration_id = ArbitrationID(source_address=msg.arbitration_id.pgn.pdu_specific) @@ -507,16 +654,50 @@ def _process_rts(self, msg): _data = [0x11, msg.data[4], 0x01, 0xFF, 0xFF] _data.extend(msg.data[5:]) logger.debug("send CTS: AID: %s" % _cts_arbitration_id) - cts_msg = Message(extended_id=True, arbitration_id=_cts_arbitration_id.can_id, data=_data, + cts_msg = Message(is_extended_id=True, arbitration_id=_cts_arbitration_id.can_id, data=_data, dlc=8) # send clear to send logger.debug("send CTS: %s" % cts_msg) - self.can_bus.send(cts_msg) + try: + self.can_bus.send(cts_msg) + except CanError: + if self._ignore_can_send_error: + pass + raise return + """ + # + # MIL: This is the wrong way around this, I should have a node assigned. + # + elif _listener is None and l_notifier is not None: + logger.debug("7, dest=0x%x" % (msg.arbitration_id.source_address)) + # find a Node object so we can search its list of known node addresses + # for this node - if we find it we are responsible for sending the CTS message + if msg.arbitration_id.pgn.pdu_specific : + _cts_arbitration_id = ArbitrationID(source_address=msg.arbitration_id.pgn.pdu_specific) + _cts_arbitration_id.pgn.value = PGN_TP_CONNECTION_MANAGEMENT + _cts_arbitration_id.pgn.pdu_specific = msg.arbitration_id.source_address + _cts_arbitration_id.destination_address = msg.arbitration_id.source_address + _data = [0x11, msg.data[4], 0x01, 0xFF, 0xFF] + _data.extend(msg.data[5:]) + logger.debug("send CTS: AID: %s" % _cts_arbitration_id) + cts_msg = Message(is_extended_id=True, arbitration_id=_cts_arbitration_id.can_id, data=_data, + dlc=8) + + # send clear to send + logger.debug("send CTS: %s" % cts_msg) + self.can_bus.send(cts_msg) + return + """ + def _process_cts(self, msg): logger.debug("_process_cts") + logger.debug("MIL8: cts message is: %s" % msg) + #logger.debug("MIL8: len(pdu-send-buffer) = %d" % len(self._incomplete_transmitted_pdus[0][23])) + + if msg.arbitration_id.pgn.pdu_specific in self._incomplete_transmitted_pdus: if msg.arbitration_id.source_address in self._incomplete_transmitted_pdus[ msg.arbitration_id.pgn.pdu_specific]: @@ -526,7 +707,18 @@ def _process_cts(self, msg): end_index = start_index + msg.data[1] for _msg in self._incomplete_transmitted_pdus[msg.arbitration_id.pgn.pdu_specific][ msg.arbitration_id.source_address][start_index:end_index]: - self.can_bus.send(_msg) + logger.debug("MIL8: msg=%s" % (_msg)) + # TODO: Needs to be pacing if we get this working... + try: + # Shouldent send a J1939 PDU as a CAN Message unless we are careful + canMessage = Message(arbitration_id=_msg.arbitration_id, data=_msg.data) + self.can_bus.send(canMessage) + except CanError: + + if self._ignore_can_send_error: + pass + raise + logger.debug("MIL8: _process_cts complete") def _process_eom_ack(self, msg): logger.debug("_process_eom_ack") @@ -580,14 +772,20 @@ def _process_abort(self, msg): msg.arbitration_id.source_address] def _throttler_function(self): - while self.can_notifier.running.is_set(): + while self.can_notifier._running: _msg = None try: _msg = self._long_message_segment_queue.get(timeout=0.1) except Empty: pass if _msg is not None: - self.can_bus.send(_msg) + try: + self.can_bus.send(_msg) + time.sleep(0.05) + except CanError: + if self._ignore_can_send_error: + pass + raise @property def transmissions_in_progress(self): diff --git a/j1939/arbitrationid.py b/j1939/arbitrationid.py index 86c74b7..1c55ad9 100644 --- a/j1939/arbitrationid.py +++ b/j1939/arbitrationid.py @@ -1,8 +1,11 @@ +import logging +import inspect + + from j1939.pgn import PGN from j1939.constants import * import logging -logger = logging.getLogger(__name__) - +logger = logging.getLogger("j1939") class ArbitrationID(object): @@ -21,85 +24,104 @@ def __init__(self, priority=7, pgn=None, source_address=0, destination_address=N Between 0 and 255. Will trrow a ValueError if PGN does not allow a dest """ + self._pgn = None self.priority = priority - if pgn == None: - pgn = PGN() + self.destination_address_value = None - if pgn and (not isinstance(pgn, PGN)): - ValueError("pgn must have PGN type") - self.pgn = pgn + logger.debug("ArbitrationID:__init__: self._pgn=%s, type %s" % (pgn, type(pgn))) - self.destination_address_value = None - if pgn: - if self.pgn.is_destination_specific: + if pgn is None: + self._pgn = PGN() + elif pgn and isinstance(pgn, int): + self._pgn = PGN.from_value(pgn) + elif pgn and isinstance(pgn, PGN): + self._pgn = pgn + else: + ValueError("pgn must have convertable type") + + + #self.pgn = pgn + + logger.debug("ArbitrationID:__init__: self._pgn=%s, type %s" % (self._pgn, type(self._pgn))) + if self._pgn: + if self._pgn.is_destination_specific: if destination_address is None: self.destination_address_value = DESTINATION_ADDRESS_GLOBAL else: if destination_address >= 0 and destination_address <= 255: self.destination_address_value = destination_address - if self.destination_address_value != pgn.pdu_specific: - logger.info("self.destination_address_value = %x, pgn.pdu_specific = %x" % - (self.destination_address_value, pgn.pdu_specific)) - - assert( self.destination_address_value == pgn.pdu_specific) + if self.destination_address_value != self._pgn.pdu_specific: + logger.debug("self._pgn=%s, self.destination_address_value = %x, pgn.pdu_specific = %x" % + (self._pgn, self.destination_address_value, self._pgn.pdu_specific)) +# assert( self.destination_address_value == pgn.pdu_specific) else: - raise ValueError("desttiantion address must be in range (0-255)") + raise ValueError("destination address must be in range (0-255)") self.source_address = source_address @property def can_id(self): - logger.info("can_id property: self.pgn.is_destination_specific=%s" % self.pgn.is_destination_specific) - - if self.pgn.is_destination_specific: - logger.info("can_id: self.pgn.is_destination_specific, dest=%x, pgn_value=%x, pdu_format=0x%x, pdu_specific=0x%x, pri=%x" % - (self.destination_address_value, - self.pgn.value, - self.pgn.pdu_format, - self.pgn.pdu_specific, + logger.info("{} property: self._pgn.is_destination_specific={}/pgn={}".format(inspect.stack()[0][3], self._pgn.is_destination_specific, self._pgn)) + + if self._pgn.is_destination_specific: + logger.info("can_id: self._pgn.is_destination_specific, dest={}, pgn_value={}, pdu_format=0x{:04x}, pdu_specific=0x{:02x}, pri={}".format( + self.destination_address_value, + self._pgn.value, + self._pgn.pdu_format, + self._pgn.pdu_specific, self.priority)) - retval = (self.source_address + - ((self.pgn.value & 0xff00) + (self.destination_address_value) << 8)+ - (self.priority << 26)) - logger.info("can_id: retval=0x%08x" % (retval)) + if self.destination_address_value: + logger.info("can_id: self.destination_address_value: pgn_value: {:04x}".format(self._pgn.value)) + logger.info(" (self._pgn.value & 0x3ff00): {:04x}".format((self._pgn.value & 0x3ff00))) + logger.info(" (self.destination_address_value): {:04x}".format((self.destination_address_value))) + retval = (self.source_address + + ((self._pgn.value & 0x3ff00) + (self.destination_address_value) << 8) + + (self.priority << 26)) + else: + logger.info("can_id: NOT self.destination_address_value:") + retval = (self.source_address + + ((self._pgn.value & 0x3ff00) << 8)+ + (self.priority << 26)) + + logger.info("can_id: retval=0x{:08x}".format(retval)) return retval else: - logger.info("can_id: NOT! self.pgn.is_destination_specific") - return (self.source_address + (self.pgn.value << 8) + (self.priority << 26)) + logger.info("can_id: NOT! self._pgn.is_destination_specific") + return (self.source_address + (self._pgn.value << 8) + (self.priority << 26)) @can_id.setter def can_id(self, canid): """ Int between 0 and (2**29) - 1 """ - logger.info("can_id setter: canid=0x%08x" % (canid)) + logger.info("{} setter: canid=0x{:08x}".format(inspect.stack()[0][3], canid)) self.priority = (canid & 0x1C000000) >> 26 - self.pgn = PGN().from_can_id(canid) + self._pgn = PGN().from_can_id(canid) self.source_address = canid & 0x000000FF - if self.pgn.is_destination_specific: + if self._pgn.is_destination_specific: self.destination_address_value = (canid & 0x0000FF00) >> 8 - logger.info("can_id: canid=0x%08x, priority=%x, pdu_format=%x, pdu_specific=%x, src=%x" % - (canid, + logger.info("{} setter: canid=0x{:08x}, priority={:x}, pdu_format={:x}, pdu_specific={:x}, src={:x}".format(inspect.stack()[0][3], + canid, self.priority, - self.pgn.pdu_format, - self.pgn.pdu_specific, + self._pgn.pdu_format, + self._pgn.pdu_specific, self.source_address)) @property def destination_address(self): - if self.pgn.is_destination_specific: + if self._pgn.is_destination_specific: return self.destination_address_value else: return None @destination_address.setter - def destination_address(self, addr): - if not self.pgn.is_destination_specific: - raise ValueError("PGN is not dest specific: {:04x}".format(self.pgn)) + def destination_address(self, value): + if not self._pgn.is_destination_specific: + raise ValueError("PGN is not dest specific: {:04x}".format(self._pgn)) else: - self.destination_address_value = addr + self.destination_address_value = value @property @@ -116,11 +138,16 @@ def pgn(self, other): self._pgn = other def __str__(self): - logger.info("arbitrationid.__str__: pri:%s, pgn:%s, dest:%s, src:%s" % - (self.priority, self.pgn, self.destination_address_value, self.source_address)) - if self.pgn.is_destination_specific: - retval = "PRI=%d PGN=%6s DST=0x%.2x SRC=0x%.2x" % ( - self.priority, self.pgn, self.destination_address_value, self.source_address) + logger.debug("arbitrationid.__str__: ids:%d, pri:%s, pgn:%s, dest:%s, src:%s, ids:%d" % + (self._pgn.is_destination_specific, self.priority, self._pgn, self.destination_address_value, self.source_address, self._pgn.is_destination_specific)) + + if self._pgn.is_destination_specific: + if self.destination_address_value is not None: + retval = "PRI=%d PGN=%6s DST=0x%.2x SRC=0x%.2x" % ( + self.priority, self._pgn, self.destination_address_value, self.source_address) + else: + retval = "PRI=%d PGN=%6s DST=NONE(error) SRC=0x%.2x" % ( + self.priority, self._pgn, self.source_address) else: - retval = "PRI=%d PGN=%6s SRC=0x%.2x " % (self.priority, self.pgn, self.source_address) + retval = "PRI=%d PGN=%6s SRC=0x%.2x" % (self.priority, self._pgn, self.source_address) return retval diff --git a/j1939/constants.py b/j1939/constants.py index 4f4e34c..c68f3ca 100644 --- a/j1939/constants.py +++ b/j1939/constants.py @@ -1,6 +1,7 @@ # transport protocol PGNs PGN_TP_CONNECTION_MANAGEMENT = 0xec00 PGN_TP_DATA_TRANSFER = 0xeb00 +PGN_TP_SEED_REQUEST = 0xd400 # address claim PGNs PGN_AC_ADDRESS_CLAIMED = 0xee00 @@ -13,7 +14,8 @@ PGN_TP_DATA_TRANSFER: "PGN_TP_DATA_TRANSFER", PGN_AC_ADDRESS_CLAIMED: "PGN_AC_ADDRESS_CLAIMED", PGN_AC_COMMANDED_ADDRESS: "PGN_AC_COMMANDED_ADDRESS", - PGN_REQUEST_FOR_PGN: "PGN_REQUEST_FOR_PGN" + PGN_REQUEST_FOR_PGN: "PGN_REQUEST_FOR_PGN", + PGN_TP_SEED_REQUEST: "PGN_TP_SEED_REQUEST" } DESTINATION_ADDRESS_NULL = 254 diff --git a/j1939/node.py b/j1939/node.py index 934a891..9ef56d8 100644 --- a/j1939/node.py +++ b/j1939/node.py @@ -1,15 +1,12 @@ import logging -log = logging.getLogger('py1939.node') -log.debug('Loading J1939 node') - from can import Listener, CanError from j1939.constants import * from j1939.pdu import PDU from j1939.nodename import NodeName -logger = logging.getLogger(__name__) -logger.debug("loading ", __name__) +logger = logging.getLogger("j1939") +logger.debug("loading %s", __name__) @@ -54,27 +51,30 @@ def address(self): return self.known_node_addresses[self.node_name.value] @property - def address(self): + def addressList(self): return self.address_list def start_address_claim(self): - logging.debug("start_address_claim:") + logger.debug("start_address_claim:") self.claim_address(self.address_list[self._current_address_index]) def claim_address(self, address): - logging.debug("claim_address:") + logger.debug("claim_address:") claimed_address_pdu = self._pdu_type() claimed_address_pdu.arbitration_id.pgn.value = PGN_AC_ADDRESS_CLAIMED claimed_address_pdu.arbitration_id.priority = 4 claimed_address_pdu.arbitration_id.pgn.pdu_specific = 0xff claimed_address_pdu.arbitration_id.source_address = address + claimed_address_pdu.arbitration_id.destination_address_value = 0xff + claimed_address_pdu.data = self.node_name.bytes self.known_node_addresses[self.node_name.value] = address + logger.info('claimed_address_pdu: %s' % claimed_address_pdu) self.bus.send(claimed_address_pdu) def on_message_received(self, pdu): if pdu.pgn == PGN_AC_ADDRESS_CLAIMED: - log.debug('got PGN_AC_ADDRESS_CLAIMED pdu') + logger.debug('got PGN_AC_ADDRESS_CLAIMED pdu') if pdu.source != DESTINATION_ADDRESS_NULL: if pdu.data != self.node_name.bytes: if pdu.source != self.address: @@ -97,7 +97,7 @@ def on_message_received(self, pdu): node_name.bytes = pdu.data self.known_node_addresses[node_name.value] = pdu.source elif pdu.pgn == PGN_AC_COMMANDED_ADDRESS: - log.debug('got PGN_AC_COMMANDED_ADDRESS pdu') + logger.debug('got PGN_AC_COMMANDED_ADDRESS pdu') node_name = NodeName() node_name.bytes = pdu.data[:8] new_address = pdu.data[8] @@ -105,7 +105,7 @@ def on_message_received(self, pdu): # if we are the commanded node change our address self.claim_address(new_address) elif pdu.pgn == PGN_REQUEST_FOR_PGN: - log.debug('got PGN_REQUEST_FOR_PGN pdu') + logger.debug('got PGN_REQUEST_FOR_PGN pdu') pgn = int("%.2X%.2X%.2X" % (pdu.data[2], pdu.data[1], pdu.data[0]), 16) if pdu.destination in (self.address, DESTINATION_ADDRESS_GLOBAL): if pgn == PGN_AC_ADDRESS_CLAIMED: @@ -122,10 +122,10 @@ def send_parameter_group(self, pgn, data, destination_device_name=None): :param destination_device_name: Should be None, or an int between 0 and (2 ** 64) - 1 """ - log.debug('send_parameter_group:') + logger.debug('send_parameter_group:') # if we are *allowed* to send data if self.known_node_addresses[self.node_name.value] not in (ADDRESS_UNCLAIMED, DESTINATION_ADDRESS_NULL): - log.debug('send_parameter_group: claimed address') + logger.debug('send_parameter_group: claimed address') pdu = self._pdu_type() pdu.arbitration_id.pgn.value = pgn pdu.arbitration_id.source_address = self.known_node_addresses[self.node_name.value] diff --git a/j1939/notifier.py b/j1939/notifier.py index b6c0506..bfc3bc4 100644 --- a/j1939/notifier.py +++ b/j1939/notifier.py @@ -1,12 +1,19 @@ import threading +import logging try: import queue except ImportError: import Queue as queue + +from can import CanError +from can.notifier import Notifier as canNotifier +import socket #same as a CAN Notifier but will listen to a queue #recv function. +logger = logging.getLogger("j1939") + class Notifier(object): def __init__(self, queue, listeners, timeout=None): @@ -45,4 +52,28 @@ def rx_thread(self): for listener in self.listeners: listener.stop() - +class CanNotifier(canNotifier): + def _rx_thread(self, bus): + msg = None + try: + while self._running: + if msg is not None: + with self._lock: + for callback in self.listeners: + callback(msg) + msg = bus.recv(self.timeout) + logger.debug('CanNotifier: {}\n'.format(msg)) + + # + # The next two handlers are intended to mask race conditions that can occur when + # we are blocked on a can-receive and close the bus. + except CanError as err: + if self._running: + raise + except ValueError as err: + if self._running: + raise + + except Exception as exc: + self.exception = exc + raise diff --git a/j1939/pdu.py b/j1939/pdu.py index 1e7253a..f688857 100644 --- a/j1939/pdu.py +++ b/j1939/pdu.py @@ -5,7 +5,7 @@ from .constants import pgn_strings, PGN_AC_ADDRESS_CLAIMED from .nodename import NodeName -logger = logging.getLogger(__name__) +logger = logging.getLogger("j1939") RADIX_DECIMAL=10 RADIX_HEX=16 @@ -34,7 +34,7 @@ def __init__(self, timestamp=0.0, arbitration_id=None, data=None, info_strings=N if arbitration_id: assert(isinstance(arbitration_id, ArbitrationID)) self.arbitration_id = arbitration_id - self.data = self._check_data(data) + self._data = self._check_data(data) self.info_strings = info_strings self.radix=RADIX_DECIMAL @@ -44,7 +44,7 @@ def __eq__(self, other): return False if self.pgn != other.pgn: return False - if self.data != other.data: + if self._data != other.data: return False if self.source != other.source: return False @@ -53,8 +53,12 @@ def __eq__(self, other): return True @property - def dat(self): - return self.data + def data(self): + return self._data + + @data.setter + def data(self, data): + self._data = data @property def pgn(self): @@ -68,11 +72,21 @@ def destination(self): """Destination address of the message""" return self.arbitration_id.destination_address + @destination.setter + def destination(self, destination): + """Destination address of the message""" + self.arbitration_id.destination_address = destination + @property def source(self): """Source address of the message""" return self.arbitration_id.source_address + @source.setter + def source(self, source): + """Source address of the message""" + self.arbitration_id.source_address = source + @property def is_address_claim(self): return self.pgn == PGN_AC_ADDRESS_CLAIMED @@ -97,6 +111,7 @@ def _check_data(self, value): for element in value: if isinstance(element, str): element = int(element) + #logger.warn("!!! element=%s type(element)=%s" % (element, type(element))) assert element >= 0, 'Data values must be between 0 and 255, element={}'.format(element) assert element <= 255, 'Data values must be between 0 and 255' return value @@ -160,6 +175,8 @@ def __str__(self): :return: A string representation of this message. """ + logger.debug("PI07: stringify PDU") + if self.radix == RADIX_HEX: data_string = " ".join("{:02x}".format(byte) for byte in self.data) else: diff --git a/j1939/pgn.py b/j1939/pgn.py index e701539..4305ffc 100644 --- a/j1939/pgn.py +++ b/j1939/pgn.py @@ -1,8 +1,7 @@ import logging -logger = logging.getLogger(__name__) - - +import inspect +logger = logging.getLogger("j1939") class PGN(object): @@ -14,7 +13,12 @@ def __init__(self, reserved_flag=False, data_page_flag=False, pdu_format=0, pdu_ @property def is_pdu1(self): - return ((self.pdu_format < 240) or self.reserved_flag or self.data_page_flag) + result = (((self.pdu_format & 0xFF) < 240) or self.reserved_flag) + logger.debug("PGN is_pdu1 {:04x}: {}".format(self.pdu_format, result)) + logger.debug(" (self.pdu_format & 0xFF) < 240 {:04x}: {}".format(self.pdu_format, (self.pdu_format & 0xFF) < 240)) + logger.debug(" self.reserved_flag {:04x}: {}".format(self.pdu_format, self.reserved_flag)) + logger.debug(" self.data_page_flag {:04x}: {}".format(self.pdu_format, self.data_page_flag)) + return result @property def is_pdu2(self): @@ -22,12 +26,19 @@ def is_pdu2(self): @property def is_destination_specific(self): - return self.is_pdu1 + result = self.is_pdu1 + logger.debug(f"PGN is_destination_specific {self.value:04x}: {result}") + return result @property def value(self): _pgn_flags_byte = ((self.reserved_flag << 1) + self.data_page_flag) - return int("%.2x%.2x%.2x" % (_pgn_flags_byte, self.pdu_format, self.pdu_specific), 16) + # + # Be sure to truncate the PGN at 18 bits + # + result = int("%.2x%.2x%.2x" % (_pgn_flags_byte & 0x03, self.pdu_format, self.pdu_specific), 16) + logger.debug(f"value-result = {result:06x}") + return result @value.setter def value(self, value): @@ -35,10 +46,11 @@ def value(self, value): self.data_page_flag = (value & 0x010000) >> 16 self.pdu_format = (value & 0x00FF00) >> 8 self.pdu_specific = value & 0x0000FF + #MIL logger.debug("PGN.@valueSetter, value=0x%08x, pdu_format=0x%08x" % (value, self.pdu_format)) @staticmethod def from_value(pgn_value): - logger.info("PGN.@from_value, pgn_value=0x%08x" % (pgn_value)) + logger.debug("PGN.@from_value, pgn_value=0x%08x" % (pgn_value)) pgn = PGN() pgn.reserved_flag = (pgn_value & 0x020000) >> 17 pgn.data_page_flag = (pgn_value & 0x010000) >> 16 @@ -48,28 +60,39 @@ def from_value(pgn_value): @staticmethod def from_can_id(canid): - logger.info("PGN.@from_can_id, value=0x%08x" % (canid)) + logger.info("{} staticmethod: canid=0x{:08x}".format(inspect.stack()[0][3], canid)) canid = canid>>8 pgn = PGN() - logger.info("PGN.@from_can_id, value=0x%08x" % (canid)) + pgn.reserved_flag = (canid & 0x020000) >> 17 pgn.data_page_flag = (canid & 0x010000) >> 16 pgn.pdu_format = (canid & 0x00FF00) >> 8 pgn.pdu_specific = canid & 0x0000FF - logger.info("PGN.@from_can_id, res=%d, dp=%d, pdu_format=0x%02x, pdu_specific=0x%02x" % - (pgn.reserved_flag, pgn.data_page_flag, pgn.pdu_format, pgn.pdu_specific)) + logger.info("{} staticmethod: PGN Creation, res={}, dp={}, pdu_format=0x{:02x}, pdu_specific=0x{:02x}".format(inspect.stack()[0][3], + pgn.reserved_flag, + pgn.data_page_flag, + pgn.pdu_format, + pgn.pdu_specific)) + return pgn def __str__(self): - retval = ("0x%.4x " % ((((self.pdu_format)<<8) | (self.pdu_specific)) & 0xFFFF)) + retval = ("0x%.5x " % ((((self.pdu_format)<<8) | (self.pdu_specific)) & 0x3FFFF)) if self.reserved_flag: retval += "R " else: retval += " " + if self.data_page_flag: retval += "P " else: retval += " " + if 0: + if self.is_destination_specific: + retval += "DS " + else: + retval += "!DS" + return retval diff --git a/j1939/utils.py b/j1939/utils.py new file mode 100644 index 0000000..b1750a4 --- /dev/null +++ b/j1939/utils.py @@ -0,0 +1,299 @@ +from __future__ import print_function +import j1939 +import logging +import inspect +import sys + +logger = logging.getLogger("j1939") +# +# for responding to seed/key requests provide your own keyGenerator +# class.. +# mine is returned in a SeedToKey meghod of a Genkey class that sits elsewhere +# on my PYTHONPATH and is propriatiry +# +# TODO: use a better baseclass override model. +# +try: + import genkey + security250 = genkey.GenKey(speed=250) + security500 = genkey.GenKey(speed=500) + logger.info("Private Genkey Loaded") +except: + # Stuff in a fake genKey responder. Pretty much just needs a + # reference to any class that can convert a Seed to a Key.. For + # obvious reasons I'm not posting mine + logger.warning("Genkey Not loaded, This one will generate garbage keys") + class Genkey: + def SeedToKey(self, seed): + return 0x12345678 + + security = Genkey() + +def set_mem_object(pointer, extension, value, channel='can0', bustype='socketcan', length=4, src=0, dest=0x17, speed=250, bus=None, timeout=10): + countdown = timeout + result = -1 + close = False + + logger.debug("------------------------------- Set Mem Object: speed={}, security250={}, security500={}".format(speed, security250, security500)) + + keygetFunction = None + + if int(speed) == 250: + keygetFunction = security250.SeedToKey + logger.debug("PI02f speed=500, keygetFunction={}".format(keygetFunction)) + elif int(speed) == 500: + keygetFunction = security500.SeedToKey + logger.debug("PI02f speed=500, keygetFunction={}".format(keygetFunction)) + else: + logger.debug("PI02f speed=Unknown, keygetFunction={}".format(keygetFunction)) + pass + + logger.debug("------------------------------- keygetFunction={}".format(keygetFunction)) + + # only watch for the memory object pgn's + filt = [{'pgn':0xd800, 'source':dest},{'pgn':0xd400, 'source':dest}] + + if bus is None: + bus = j1939.Bus(channel=channel, bustype=bustype, timeout=0.01, keygen=keygetFunction, speed=speed, bitrate=(int(speed)*1000), broadcast=False, j1939_filters=filt) + node = j1939.Node(bus, j1939.NodeName(), [src]) + bus.connect(node) + close = True + + pLow = pointer & 0x0000FF + pMid = (pointer >> 8) & 0x0000FF + pHigh = (pointer >> 16) & 0x0000FF + + dm14data = [length, 0x15, pLow, pMid, pHigh, extension, 0xff, 0xff] + + dm14pgn = j1939.PGN(pdu_format=0xd9, pdu_specific=dest) + dm14aid = j1939.ArbitrationID(pgn=dm14pgn, source_address=src, destination_address=dest) + dm14pdu = j1939.PDU(timestamp=0.0, arbitration_id=dm14aid, data=dm14data, info_strings=None) + dm14pdu.display_radix='hex' + + bus.send(dm14pdu) + + sendBuffer = [] + logger.info("---------------## length=%d, value=%s " % (length, value)) + if isinstance(value, int) and length < 8: + logger.info("-----value") + if length < 8: + sendBuffer.append(length) + for i in range(0, length): + sendBuffer.append((value >> (8*i)) & 0xff) + else: + raise ValueError("Don't know how to send a %d byte integer" % length) + + elif isinstance(value, list): + # if sending a list use the exact list elements as the data + logger.info("-----list of {} byte(s)".format(len(value))) + sendBuffer.append(len(value)) + for i in range(len(value)): + logger.info("---------------## b[{}]=0x{:02x}".format(i, value[i])) + sendBuffer.append(value[i]) + + elif isinstance(value, str): + logger.info("-----str, value={}, len(value)={}. length={}".format(value, len(value), length)) + assert(len(value) <= length) + sendBuffer.append(length+1) + for i in range(len(value)): + sendBuffer.append(ord(value[i])) + + else: + raise ValueError("Data type not supported.") + + logger.info("---------------## sendBuffer=%s ", sendBuffer) + + dm16pgn = j1939.PGN(pdu_format=0xd7, pdu_specific=dest) + dm16aid = j1939.ArbitrationID(pgn=dm16pgn, source_address=src, destination_address=dest) + dm16pdu = j1939.PDU(timestamp=0.0, arbitration_id=dm16aid, data=sendBuffer) + dm16pdu.display_radix='hex' + + logger.info("----------------## PDU=%s ", dm16pdu) + + # Wait around for a while looking for the second proceed + while countdown: + countdown -= 1 + rcvPdu = bus.recv(timeout=0.25) + if rcvPdu: + rcvPdu.display_radix='hex' + logger.debug("received PDU: %s", rcvPdu) + if rcvPdu.pgn == 0xd800: + if rcvPdu.data[0]==1 and rcvPdu.data[1]==0x11: + if rcvPdu.data[6] == 0xff and rcvPdu.data[7] == 0xff: + bus.send(dm16pdu) + logger.info('Sent %s', dm16pdu) + elif rcvPdu.data[0]==0 and rcvPdu.data[1]==0x19: + logger.info("Value Sent") + result = 1 + break + elif rcvPdu.data[0]==0 and rcvPdu.data[1]==0x1B: + logger.info("Rejected") + result = 0 + break + if close: + bus.shutdown() + return result + +def get_mem_object(pointer, extension, channel='can0', bustype='socketcan', length=4, src=0, dest=0x17, bus=None, speed=250, timeout=10): + logger.info("{}: begin".format(inspect.stack()[0][3])) + countdown = timeout + result = None + close = False + + if bus is None: + bus = j1939.Bus(channel=channel, bustype=bustype, timeout=0.01, speed=speed, bitrate=(int(speed)*1000), broadcast=False) + node = j1939.Node(bus, j1939.NodeName(), [src]) + bus.connect(node) + close = True + + pgn = j1939.PGN() + pgn.value = 0xd900 + dest # Request a DM14 mem-object + aid = j1939.ArbitrationID(pgn=pgn, source_address=src, destination_address=dest) + + # Get the 3 bytes of the pointer and put them in the correct locations + pointer0 = pointer & 0xff + pointer1 = (pointer & 0xff00) >> 8 + pointer2 = (pointer & 0xff0000) >> 16 + + data = [length, 0x13, pointer0, pointer1, pointer2, extension, 0xff, 0xff] + pdu = j1939.PDU(timestamp=0.0, arbitration_id=aid, data=data, info_strings=None) + assert(pdu != None) + pdu.display_radix='hex' + logger.info("{}: Sending Request PDU: {}".format(inspect.stack()[0][3], pdu)) + bus.send(pdu) + + # + # Wait for the response + # + while countdown: + pdu = bus.recv(timeout=1) + if pdu is not None: + logger.info("{}: Received PDU: {}".format(inspect.stack()[0][3], pdu)) + logger.info(pdu) + if pdu.pgn == 0xd700: + value = list(pdu.data) + length = value[0] + if length == 1: + result = value[1] + elif length == 2: + result = (value[2] << 8) + value[1] + elif length == 4: + result = (value[4] << 24) + (value[3] << 16) + (value[2] << 8) + value[1] + else: + result = value[1:] + + #logger.info("{}: d700 received, result: {}/0x{:08x}".format(inspect.stack()[0][3], result, result)) + + break # got what I was waiting for + + countdown -= 1 + + if close: + logger.info("{}: Closing Bus".format(inspect.stack()[0][3])) + bus.shutdown() + + if result is None: + raise IOError(" no CAN response") + + + return result + +def request_pgn(requested_pgn, channel='can0', speed=250, bustype='socketcan', length=4, src=0, dest=0x17, bus=None, timeout=10): + countdown = timeout + result = None + close = False + + keygetFunction = None + if speed == 250: + keygetFunction = security250.SeedToKey + if speed == 500: + keygetFunction = security500.SeedToKey + + if not isinstance(requested_pgn, int): + raise ValueError("pgn must be an integer.") + + if bus is None: + bus = j1939.Bus(channel=channel, bustype=bustype, timeout=0.01, speed=speed, bitrate=(int(speed)*1000), keygen=keygetFunction, broadcast=False) + node = j1939.Node(bus, j1939.NodeName(), [src]) + bus.connect(node) + close = True + + pgn = j1939.PGN() + pgn.value = 0xea00 + dest # request_pgn mem-object + aid = j1939.ArbitrationID(pgn=pgn, source_address=src, destination_address=dest) + + pgn0 = requested_pgn & 0xff + pgn1 = (requested_pgn >> 8) & 0xff + pgn2 = (requested_pgn >> 16) & 0xff + + data = [pgn0, pgn1, pgn2] + pdu = j1939.PDU(timestamp=0.0, arbitration_id=aid, data=data, info_strings=None) + + pdu.display_radix='hex' + + bus.send(pdu) + maxPdu = 50 + while countdown: + pdu = bus.recv(timeout=1) + if pdu and (pdu.pgn == 0xe800 or pdu.pgn == requested_pgn): + result = list(pdu.data) + break # got what I was waiting for + + elif pdu: + maxPdu -=1 + if maxPdu <= 0: + raise IOError('Bus too busy') + elif pdu is None: + countdown -= 1 + else: + raise Exception('WHAT HAPPENED') + if close: + bus.shutdown() + if not result: + raise IOError(" no CAN response") + return result + + +def send_pgn(requested_pgn, data, channel='can0', speed=250, bustype='socketcan', length=4, src=0, dest=0x17, bus=None, timeout=10): + countdown = timeout + result = None + close = False + + keygetFunction = None + if speed == 250: + keygetFunction = security250.SeedToKey + if speed == 500: + keygetFunction = security500.SeedToKey + + if not isinstance(requested_pgn, int): + raise ValueError("pgn must be an integer.") + if bus is None: + bus = j1939.Bus(channel=channel, bustype=bustype, speed=speed, bitrate=(int(speed)*1000), timeout=0.01, keygen=keygetFunction) + close = True + + pgn = j1939.PGN() + if requested_pgn < 0xf000: + requested_pgn |= dest + pgn.value = requested_pgn#0xea00 + dest # request_pgn mem-object + aid = j1939.ArbitrationID(pgn=pgn, source_address=src, destination_address=dest) + + logger.info(data) + pdu = j1939.PDU(timestamp=0.0, arbitration_id=aid, data=data, info_strings=None) + + pdu.display_radix='hex' + + bus.send(pdu) + if close: + bus.shutdown() + if 0: #leaving in miller's if 0 + while countdown: + pdu = bus.recv(timeout=1) + if pdu and (pdu.pgn == 0xe800 or pdu.pgn == requested_pgn): + result = list(pdu.data) + break # got what I was waiting for + if pdu: + countdown -= 1 + if not result: + raise IOError(" no CAN response") + return result + diff --git a/setup.py b/setup.py index 9cf87f0..56c5161 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import logging from setuptools import setup, find_packages -__version__ = "0.1.0-alpha.1" +__version__ = "0.1.0-alpha.3" logging.basicConfig(level=logging.WARNING) @@ -24,9 +24,15 @@ "doc": ["*.*"] }, - install_requires=["python-can>=1.5"], + install_requires=["python-can>=2.0.0a2"], - scripts=["./bin/j1939_logger.py"], + scripts=[ + "./bin/j1939_logger.py", + "./bin/j1939_mem_query.py", + "./bin/j1939_mem_set.py", + "./bin/j1939_request_pgn.py", + "./bin/j1939_send_pgn.py" + ], # Tests can be run using `python setup.py test` test_suite="nose.collector",