Re-evaluating a string?

Discussion in 'Python' started by bugnthecode, Jul 23, 2006.

  1. bugnthecode

    bugnthecode Guest

    I'm writing a program to send data over the serial port. I'm using
    pyserial, and I'm on WindowsXP. When I use literals I can get the data
    accross how I want it for example:

    1 2 3 4 5 6
    serialport.write('!SC'+'\x01'+'\x05'+'\xFA'+'\x00'+'\r')

    1=Get devices attention
    2=Select channel on device
    3=Rate for movement
    4=Low byte of 16 bits
    5=High bytes of 16 bits
    6=Carriage return signaling command is over

    This command works as desired. Sends the first 3 ASCII characters, then
    some numbers in hex followed by a carriage return.

    My problem is that the "write()" function only takes a string, and I
    want to substitute variables for the hex literals.

    I know that I can use the "hex()" function and it will return a string
    with the appropriate hex value, and I could combine this with some
    other literals like "\\" to create my desired hex literal, but then I
    would need something to re-parse my string to change my ASCII text into
    the appropriate hex values.

    Any ideas on how I may do this? Any help is greatly appreciated.

    Thanks,
    Will
    bugnthecode, Jul 23, 2006
    #1
    1. Advertising

  2. bugnthecode

    Tim Chase Guest

    > serialport.write('!SC'+'\x01'+'\x05'+'\xFA'+'\x00'+'\r')
    [cut]
    > My problem is that the "write()" function only takes a string, and I
    > want to substitute variables for the hex literals.


    Well, you can try something like

    >>> import types
    >>> def makeString(a):

    .... return ''.join([type(x) != types.IntType and
    .... str(x) or chr(x) for x in a])
    ....
    >>> data = ['!SC', 1, 5, 0xFA, 0, '\r']
    >>> makeString(data)

    '!SC\x01\x05\xfa\x00\r'


    Thus, you can mix and match your desired data in a list, and then
    let Python intelligently smash together the string you want, so
    you can later pass that to your write() call.

    It does hiccup (read "throw an exception") if you have an empty
    string in your list. It also falls down if you try and put in an
    integer constant that isn't in range(256). My advice regarding
    these would be "don't do that". :) Alternatively, you can
    concoct some cousin-function to chr() that takes any old garbage
    along with a default, and returns either the chr() of it, unless
    that throws an expception, in which case you just return
    something like '\x00' (whatever you specified as the default).

    This allows you to use your favorite notation. If you like hex
    notation, you can use it (as in the "0xFA" in the above data).
    If you prefer integers, you can toss them in the mix.

    Alternatively, you can create a suite of API wrapper functions,
    such as


    def move(rate, low, high, channel=1):
    serialport.write(''.join([type(x) != types.IntType and
    .... str(x) or chr(x) for x in
    .... ['!SC', channel, rate, low, high, '\r']
    .... ]))

    (that could be uncompacted a bit for readibility's sake...)

    You could then just call move(5, 0xFA, 0) and the function does
    the heavy work for you. Might also be more readable later for
    other folks coming to the project (if there are others).

    Just a couple ideas you might want to try.

    -tkc
    Tim Chase, Jul 24, 2006
    #2
    1. Advertising

  3. bugnthecode

    John Machin Guest

    bugnthecode wrote:
    > I'm writing a program to send data over the serial port. I'm using
    > pyserial, and I'm on WindowsXP. When I use literals I can get the data
    > accross how I want it for example:
    >
    > 1 2 3 4 5 6
    > serialport.write('!SC'+'\x01'+'\x05'+'\xFA'+'\x00'+'\r')
    >
    > 1=Get devices attention
    > 2=Select channel on device
    > 3=Rate for movement
    > 4=Low byte of 16 bits
    > 5=High bytes of 16 bits
    > 6=Carriage return signaling command is over
    >
    > This command works as desired. Sends the first 3 ASCII characters, then
    > some numbers in hex followed by a carriage return.
    >
    > My problem is that the "write()" function only takes a string, and I
    > want to substitute variables for the hex literals.
    >
    > I know that I can use the "hex()" function and it will return a string
    > with the appropriate hex value, and I could combine this with some
    > other literals like "\\" to create my desired hex literal, but then I
    > would need something to re-parse my string to change my ASCII text into
    > the appropriate hex values.


    No need. That would be like travelling from Brooklyn to the Bronx via
    Walla Walla, WA. And "the appropriate hex values" exist only as ASCII
    text, anyway.

    The following code (a) is untested (b) assumes each of the 3 numbers
    (channel, rate, value) is UNsigned.

    channel = 1
    rate = 5
    value = 0xFA # or value = 250
    if HARD_WAY:
    vhi, vlo = divmod(value, 256)
    command = "!SC" + chr(channel) + chr(rate) + chr(vlo) + chr(vhi) +
    "\r"
    elif MEDIUM_WAY:
    vhi, vlo = divmod(value, 256)
    command = "!SC%c%c%c%c\r" % (channel, rate, vlo, vhi)
    else:
    import struct
    command = "!SC" + struct.pack("<BBH", channel, rate, value) + "\r"
    print repr(command) # for debugging, or if you're desperate to see some
    hex values :)
    serialport.write(command)

    Do check out the manual sections relating to the struct module.

    If any value is signed, you definitely want to use the struct module
    (with "b" or "h" instead of "B" or "H" of course) instead of mucking
    about byte-bashing.

    HTH,
    John
    John Machin, Jul 24, 2006
    #3
  4. bugnthecode

    bugnthecode Guest

    Thanks Tim, and John for your quick responses!

    Tim, I tested your function and it works! Though I don't completely
    understand how. Could you possibly explain this?

    John, I test your "MEDUIM_WAY"and it works as well. How is it that
    putting the string together this way translates into a hex value to be
    transmitted to the serial port? When I manually tried to put a string
    together I couldn't get this to happen. I was trying:

    controlString = '!SC' + '\\' + ch.__str__() + '\\' + rate.__str__()
    ....etc

    also I noticed you added another line to the code which appears to
    split the low and high bytes for me? If so thanks! Could you also offer
    an explanation on how this works. I tried a google search and couldn't
    get a decent explanation. I implemented this a little differently as
    you can see in my Position class. Could you possibly offer any info on
    the benefits/drawbacks of the different methods?

    Thanks again to both of you for your quick responses and help.

    Will

    import serial
    import types

    class Position:
    def __init__(self, num):
    """Takes the position as an int, and splits the low and high
    bytes

    into instance members."""
    self.lowByte = num & 0x00FF
    self.highByte = (num & 0xFF00) >> 8

    def __str__(self):
    """Mainly for debugging purposes. Allows meaningful output when
    printed"""
    return 'Low: ' + self.lowByte.__str__() + '\nHigh: ' +
    self.highByte.__str__()

    def makeString(a):
    """Takes in a list, and intelligentlly smashes everything
    together.

    Outputs everything as a hex string.
    Posted by: Tim Chase on comp.lang.python"""
    return ''.join([type(x) != types.IntType and
    str(x) or chr(x) for x in a])

    def getVer(localpsc):
    """Gets the version from the PSC. Mainly just to verify a
    connection"""

    localpsc.write('!SCVER?\r')
    localpsc.read(8) #Discard the echo!
    s = localpsc.read(3)
    print s

    def moveServo(localpsc, ch, rate, position):
    """Takes in a serial object, the desired channel, the ramp rate,
    and
    the desired position of ther servo. Moves the servo to the desired
    postion."""

    #localpsc.write('!SC', ch, rate, position.low, position.high, '\r')
    #controlString = makeString(['!SC', ch, rate, position.lowByte,
    position.highByte, '\r'])
    #localpsc.write(controlString)
    #localpsc.flushInput() #discard the echo!

    """Following line from John Machin"""
    controlString = "!SC%c%c%c%c\r" % (ch, rate, position.lowByte,
    position.highByte)
    localpsc.write(controlString)

    psc = serial.Serial(1, 2400)

    mypos = Position(2500)

    moveServo(psc, 0, 5, mypos)
    psc.close()
    bugnthecode, Jul 24, 2006
    #4
  5. bugnthecode

    John Machin Guest

    bugnthecode wrote:
    > Thanks Tim, and John for your quick responses!
    >
    > Tim, I tested your function and it works!
    > Though I don't completely
    > understand how. Could you possibly explain this?
    >
    > John, I test your "MEDUIM_WAY"and it works as well.


    Now try the struct module approach.

    Are you 100% certain that the servo position can't be negative? What
    would you do if it could be negative?

    > How is it that
    > putting the string together this way translates into a hex value to be
    > transmitted to the serial port?


    Like I tried to tell you before, there is no such thing as transmitting
    a hex value. You are transmitting 8-bit bytes, one bit at a time. The
    number 6 might be transmitted as 00000110 or 01100000 (the latter, IIRC
    -- it's been a while). The number 6 can be represented in a computer
    program as 6 or 0x6. As your write() function expects a string, you
    need to represent it in your program as a string e.g. '\x06' or chr(6)
    or "%c" % 6.

    Read about chr() in the manual:
    http://docs.python.org/lib/built-in-funcs.html

    Read about string formatting in the manual:
    http://docs.python.org/lib/typesseq-strings.html

    > When I manually tried to put a string
    > together I couldn't get this to happen. I was trying:
    >
    > controlString = '!SC' + '\\' + ch.__str__() + '\\' + rate.__str__()
    > ...etc


    Ugh. FWIW, use str(foo) instead of foo.__str__()

    >
    > also I noticed you added another line to the code which appears to
    > split the low and high bytes for me? If so thanks! Could you also offer
    > an explanation on how this works. I tried a google search and couldn't
    > get a decent explanation.


    I presume you mean
    vhi, vlo = divmod(value, 256)
    What did you search for?
    If you are on Windows:
    do(click on Start, hover on All Programs, hover on Python 2.4,
    click on Python Manuals, type in divmod, press Enter key twice)
    else:
    deduce that divmod can only be a built-in function (I didn't
    declare it, did I?)
    read web docs on built-in functions
    (http://docs.python.org/lib/built-in-funcs.html)

    > I implemented this a little differently as
    > you can see in my Position class. Could you possibly offer any info on
    > the benefits/drawbacks of the different methods?


    No great difference. Better than both is to use the struct module.

    >
    > Thanks again to both of you for your quick responses and help.
    >
    > Will
    >
    > import serial
    > import types
    >
    > class Position:


    It is extreme overkill, IMHO, to use a class for this trivium.


    > def __init__(self, num):
    > """Takes the position as an int, and splits the low and high
    > bytes
    >
    > into instance members."""
    > self.lowByte = num & 0x00FF
    > self.highByte = (num & 0xFF00) >> 8


    If you are sure that 0 <= num <= 0xFFFF, then you don't need the 0xFF00
    mask. If you are unsure, then don't just silently transmit what may
    well be rubbish; check, or use an assertion:
    assert 0 <= num <= 0xFFFF

    >
    > def __str__(self):
    > """Mainly for debugging purposes. Allows meaningful output when
    > printed"""


    FFS. Use the repr() built-in, like I suggested.

    > return 'Low: ' + self.lowByte.__str__() + '\nHigh: ' +
    > self.highByte.__str__()
    >
    > def makeString(a):
    > """Takes in a list, and intelligentlly smashes everything
    > together.
    >
    > Outputs everything as a hex string.


    No it doesn't.

    > Posted by: Tim Chase on comp.lang.python"""
    > return ''.join([type(x) != types.IntType and
    > str(x) or chr(x) for x in a])
    >
    > def getVer(localpsc):
    > """Gets the version from the PSC. Mainly just to verify a
    > connection"""
    >
    > localpsc.write('!SCVER?\r')
    > localpsc.read(8) #Discard the echo!
    > s = localpsc.read(3)
    > print s
    >
    > def moveServo(localpsc, ch, rate, position):
    > """Takes in a serial object, the desired channel, the ramp rate,
    > and
    > the desired position of ther servo. Moves the servo to the desired
    > postion."""
    >
    > #localpsc.write('!SC', ch, rate, position.low, position.high, '\r')
    > #controlString = makeString(['!SC', ch, rate, position.lowByte,
    > position.highByte, '\r'])
    > #localpsc.write(controlString)
    > #localpsc.flushInput() #discard the echo!
    >
    > """Following line from John Machin"""
    > controlString = "!SC%c%c%c%c\r" % (ch, rate, position.lowByte,
    > position.highByte)
    > localpsc.write(controlString)
    >
    > psc = serial.Serial(1, 2400)
    >
    > mypos = Position(2500)
    >
    > moveServo(psc, 0, 5, mypos)
    > psc.close()


    import struct
    DEBUG = True

    def moveServo(localpsc, ch, rate, position):
    """Takes in a serial object, the desired channel, the ramp rate,
    and
    the desired position of the servo. Moves the servo to the
    desired
    position."""
    assert 0 <= ch <= 255 # or tighter bounds from manuf's spec
    assert 0 <= rate <= 255
    assert 0 <= position <= 65535 # aka 0xFFFF
    cmd = "!SC" + struct.pack("<BBH", ch, rate, position) + "\r"
    if DEBUG:
    print "moveServo:", repr(cmd)
    localpsc.write(cmd)

    It's that simple. If you are ever going to do more than this one
    script, it will pay to investigate the struct module. Python has
    "batteries included". You don't need to find a lead mine and look up
    "sal ammoniac" in your grandparents' encyclopedia :)

    Cheers,
    John
    John Machin, Jul 24, 2006
    #5
  6. bugnthecode

    Tim Chase Guest

    > Thanks Tim, and John for your quick responses!

    This is one of the better lists for getting quick (and usually
    helpful) responses.

    > Tim, I tested your function and it works! Though I don't completely
    > understand how. Could you possibly explain this?


    >>> def makeString(a):

    .... return ''.join([type(x) != types.IntType and
    .... str(x) or chr(x) for x in a])
    ....

    The "boolean_expression and value1 or value2" is a common python
    idiom for something akin to C/C++/Java's ternary
    "boolean_expression? value1: value2" expression. There are some
    gotchas (and workarounds for those gotchas) if value1 can be a
    "false" value (an empty string, a zero or empty list are good
    examples of this). It pretty much boils down to joining all the
    elements of the list that is composed from "for every item in the
    list 'a', if it's not an int, just return the str() of it; and if
    it is an int, return the chr() of it". It then smashes them all
    together with the join() and returns the resulting string.

    > John, I test your "MEDUIM_WAY"and it works as well. How is it that
    > putting the string together this way translates into a hex value to be
    > transmitted to the serial port? When I manually tried to put a string
    > together I couldn't get this to happen. I was trying:
    >
    > controlString = '!SC' + '\\' + ch.__str__() + '\\' + rate.__str__()


    The string-formatting "%c" expects a byte and prints the ascii
    character relating to the byte. Also a good way to do things.
    Come to think of it, John had a lot of good ideas in his post.
    In your above code, the ch.__str__() creates a string
    representation of the number I presume is stored in ch. The
    string representation of a number is simply a string containing
    that number:

    >>> x = 42
    >>> x.__str__()

    '42'

    Not very exciting. And generally stylistically better to use
    str() in favor of the __str__() method call.

    > also I noticed you added another line to the code which appears to
    > split the low and high bytes for me? If so thanks! Could you also offer
    > an explanation on how this works.


    the divmod(x,y) function divides x by y, and returns a tuple.
    The first value of the tuple is the integer result of the
    division, and the second value of the tuple is the remainder.
    It's a one-step way of doing

    hi,lo = divmod(x,y)

    works like a condensing of

    hi = x / y
    lo = x % y

    > I tried a google search and couldn't
    > get a decent explanation.


    Googling the python docs for "divmod" should do the trick.

    http://www.google.com/search?q=site:docs.python.org divmod

    returns several helpful hits wherein you can learn more than any
    sane person should care to know. But the above primer should be
    enough to get you on your way.

    I think, in the case of your example string, using John's
    suggestion of the %c formatting is the cleanest approach. As
    such, I'd rework the move() function I suggested to simply be
    something like

    def move(rate,lo,hi,chan=1):
    return "!SC%c%c%c%c\r" % (chan, rate, lo, hi)

    where you possibly even just pass in the "position" parameter,
    and let the function do the splitting with something like

    def move(rate,position,chan=1)
    hi,lo = divmod(position & 0xFFFF, 256)
    return "!SC%c%c%c%c\r" % (chan, rate, lo, hi)

    or optionally use the struct module to unpack them.

    Just a few more thoughts,

    -tkc
    Tim Chase, Jul 24, 2006
    #6
  7. bugnthecode

    John Machin Guest

    Tim Chase wrote:
    [snip]
    > As
    > such, I'd rework the move() function I suggested to simply be
    > something like
    >
    > def move(rate,lo,hi,chan=1):
    > return "!SC%c%c%c%c\r" % (chan, rate, lo, hi)
    >
    > where you possibly even just pass in the "position" parameter,
    > and let the function do the splitting with something like
    >
    > def move(rate,position,chan=1)
    > hi,lo = divmod(position & 0xFFFF, 256)
    > return "!SC%c%c%c%c\r" % (chan, rate, lo, hi)
    >
    > or optionally use the struct module to unpack them.


    Say what? We need to pack the position first, we can't use
    struct.unpack on an integer, only a string. So that ends up looking
    like this:

    def move(rate,position,chan=1):
    lo, hi = struct.unpack("<BB", struct.pack("<H", position))
    return "!SC%c%c%c%c\r" % (chan, rate, lo, hi)

    which is a bit, shall we say, unfortunate. Why unpack it when we've
    already packed it just so we can pack it with another method? This is
    getting close to dailyWTF territory. Forget that. Let's go the whole
    hog with struct.pack:

    def move(rate,position,chan=1):
    return struct.pack("<3sBBHs", "!SC", chan, rate, position, "\r")

    What I tell you 3 times is true ... just use struct.pack and stop
    faffing about. Get used to it. One day you'll have to output a negative
    1-or-two-byte integer, a four-byte integer, a float even -- no
    scratching your head about what bits to shift, what mask to use, just
    fill in the codes from the manual and away you go.

    HTH,
    John



    Cheers,
    John
    John Machin, Jul 24, 2006
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Shelly
    Replies:
    8
    Views:
    570
    Michael Borgwardt
    Dec 10, 2004
  2. Jason
    Replies:
    25
    Views:
    977
    Jorge Rivera
    Feb 22, 2004
  3. John Boik
    Replies:
    2
    Views:
    304
    Malcolm
    Aug 16, 2003
  4. Miro
    Replies:
    17
    Views:
    2,661
    Nobody
    Aug 21, 2011
  5. Asfand Yar Qazi
    Replies:
    6
    Views:
    156
    Joel VanderWerf
    Feb 17, 2005
Loading...

Share This Page