Moto V3cm module and OBEX support (was Re: USTRING "encoding" behavior different from STRING's ?)

Perry Nguyen <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <[email protected]>
Joe Pham wrote:
>> I'm in the process of writing support for my Motorola V3c (VZW), and 
>> I mostly have the OBEX support complete.
> 
> I was just about to start doing that for the V710.  If you're willing to contribute your code to the project, please post or email me your code and I'll roll it into the main trunk.

In fact, I am willing to contribute the code, I was just waiting for my 
implementation to be more complete.  For the OBEX portion, I have completed 
folder listings and object retrieval.  OBEX PUT and DELETE (variation of PUT) 
have not been implemented yet.  I would be glad to share my work.

Attached is the source of my modules.  (my com_motov3cm.py is starting to 
inherit from com_moto.py, but I actually haven't used anything from the 
superclass yet).

>> I still haven't figured out how to switch from mode OBEX back to  
>> MODEM.
> 
> Not sure what you meant by that.  Please be more specific.

Once I do AT+MODE=22, switching to OBEX mode, I am no longer able to send AT 
commands, the device will not respond; it only responds to OBEX requests, even 
once disconnected.  I have been doing port snoops of Motorola Phone tools on 
Windows and seen that it simply closes, resets and re-opens the port.  But I 
have not been able to duplicate that behavior on Linux yet.  (Then again, I 
haven't been able to spend that much time on it).

>> I noticed that there's the V710 being added recently and it does 
>> share a lot of common features that should possibly  moved to a 
>> superclass. com_motov.py maybe.
> 
> There's already a com_moto.

Right, but there is stuff in com_motov710.py that is common to my 
com_motov3cm.py, e.g. groups.

> -Joe Pham
com_motov3cm.py (text/plain, 7.1 KB)
### BITPIM
###
### Author: Perry Nguyen <[email protected]>
### I'm sure much of this can be refactored to a moto v-series base class.

"""Communicate with a Motorola V3cm"""

from DSV import DSV
from os.path import basename
from sys import exc_info
import com_moto
import com_obex

class Phone(com_moto.Phone,com_obex.ObexProtocol):
    "Talk to a Motorola V3cm"

    desc="Motorola V3cm"
    serialsname="motv3cm"
    __pb_recno = 0
    __pb_number = 1
    __pb_number_type = 2
    __pb_name = 3
    __pb_record_type = 4
    __pb_record_image = 13

    __obex_wallpapers = "picture"
    __obex_ringtones = "audio"
    # index 3 = main, but we'll make it 'home' for bitpim
    __pb_record_types = ( 'office', 'home', 'home', 'cell', \
                          'fax', 'pager' )
    # record_types actually extends to these two, but we should never hit them
    # , 'Email', 'Mailing list' )
    __pb_email_types = [6,7]
    _esn = None
    MODEMOTOROLA = "modemotorola"

    def __init__(self, logtarget, commport):
        com_moto.Phone.__init__(self, logtarget, commport)
        com_obex.ObexProtocol.__init__(self)
        self.mode=self.MODENONE

    def close(self):
        self.setmode(self.MODENONE)
        com_moto.Phone.close(self)

    def setmode(self, desired):
        if self.mode == self.MODEOBEX and desired != self.MODEOBEX:
            self.obexdisconnect()
            self.comm.write("AT\r")
            try:
                self.comm.readsome()
            except:
                info = exc_info()
                self.log("Exception while trying AT command: %s: %s" %
                         (info[0], info[1]))
        return com_moto.Phone.setmode(self, desired)

    def _setmodemotorola(self):
        self.comm.sendatcommand("E0Q0V1")
        self.comm.sendatcommand("+MODE=2")
        return True

    def _setmodenone(self):
        self.comm.sendatcommand("E0Q0V1")
        self.comm.sendatcommand("+MODE=0")
        return True

    def _setmodeobex(self):
        try:
            self.comm.sendatcommand("+MODE=22")
            self.obexconnect()
        except:
            info = exc_info()
            self.log("Exception switching to modeobex: %s: %s" %
                     (info[0], info[1]))
            try:
                self.obexdisconnect()
            except:
                info = exc_info()
                self.log("Exception cancelling obexconnect: %s: %s" %
                         (info[0], info[1]))
                pass
            return False
        return True

    def getfundamentals(self, results):
        if self._esn is None:
            self.setmode(self.MODENONE)

            # do this instead of brew to accomodate bluetooth connections
            # can't do brew over bluetooth
            s = self.comm.sendatcommand("+CGSN")
            # there seems to be a bug in sendatcommand, it has the results
            # of previous sendatcommands, either that or a bug in the phone.
            for i in s:
                if i.find("+CGSN:") != -1:
                    idx = i.index(": ")
                    self._esn = i[idx + 2:]
                    break
                
            if len(self._esn) == 0:
                self.log("WARNING: ESN not found!")
            else:
                self.log("ESN is " + self._esn)
        else:
            self.log("ESN is already cached")
        results['uniqueserial'] = self._esn

    def getwallpapers(self, results):
        self.setmode(self.MODEOBEX)
        files = self.getobexfolderlist(self.__obex_wallpapers)
        media = {}
        mediaindex = {}
        index = 0
        countFiles = len(files)
        for i in files:
            self.log("Attempting to read: %s/%s" % (self.__obex_wallpapers, i))
            self.progress(index, countFiles - 1, "Loading wallpaper: %s" % i)
            media[i] = self.getobexfile(self.__obex_wallpapers, i)
            mediaindex[index] = { 'name': i, 'origin': 'images' }
            index += 1
        results['wallpapers'] = media
        results['wallpaper-index'] = mediaindex
        self.setmode(self.MODENONE)
            
    def getringtones(self, results):
        pass
    def getcalendar(self, results):
        pass

    def getphonebook(self, results):
        self.setmode(self.MODEMOTOROLA)
        self.comm.sendatcommand("+CPBS=\"ME\"")
        entries = {}
        s = self.comm.sendatcommand("+MPBR=?")
        for j in s:
            self.log("Phone book info: %s" % j)
        for i in range(1, 1000, 15):
            upper = i + 15
            upper = min(upper, 1000)
            s = self.comm.sendatcommand("+MPBR=%d,%d" % (i, upper))
            self.progress(i, 1000, "Reading %d to %d" % (i, upper))
            for j in s:
                self.parse_pb_entry(entries, j, results)
        pbook = {}
        k = 0
        for i in entries.values():
            pbook[k] = i
            k += 1
        results['phonebook'] = pbook

    def parse_pb_entry(self, entries, entry, fundamentals):
        idx = entry.index(": ")
        entry = entry[idx + 2:]
        self.log(entry)
        e = DSV.importDSV([ entry ])[0]

        fullname = e[self.__pb_name]

        if entries.has_key(fullname):
            res = entries[fullname]
        else:
            res = {}
            res['serials'] = [ {'sourcetype': self.serialsname,
                                'sourceuniqueid': fundamentals['uniqueserial'],
                                'serial1': e[self.__pb_recno]} ]
            res['names'] = [ {'full': fullname } ]
            res['numbers'] = []
            res['emails'] = []
            res['wallpapers'] = []

        wallpaper = e[self.__pb_record_image]
        if len(wallpaper) > 0:
            res['wallpapers'].append({ 'wallpaper': basename(wallpaper),
                                       'use': 'call' })
            self.log("Using wallpaper: " + basename(wallpaper))
        number = e[self.__pb_number]
        n_type = int(e[self.__pb_record_type])
        n_type_str = self.__pb_record_types[n_type]
        speed_dial = int(e[self.__pb_recno])

        if n_type in self.__pb_email_types:
            res['emails'].append({ 'email': number })
        else:
            res['numbers'].append({ 'number': number,
                                    'type': n_type_str,
                                    'speeddial': speed_dial })
        
        entries[fullname] = res
        return res

class Profile(com_moto.Profile):
    # Declare our phone model for autodetection
    phone_manufacturer = "Motorola"
    phone_model = "Motorola CDMA V3c Phone"
    _supportedsyncs=(
        ('phonebook', 'read', None),  # all phonebook reading
        ('wallpaper', 'read', None),  # all phonebook reading
        )
    deviceclasses=("modem")
    # It seems usbids is only used if detectphone is defined?
    usbids = ( ( 0x22b8, 0x2a62, 3 ), )
    # WALLPAPER_WIDTH = 176
    # WALLPAPER_HEIGHT = 220
    # WALLPAPER_CONVERT_FORMAT = ("gif", "jpg")
    # MAX_WALLPAPER_BASENAME_LENGTH = 32
    def __init__(self):
        com_moto.Profile.__init__(self)
com_obex.py (text/plain, 11.2 KB)
### BITPIM
###
### Author: Perry Nguyen <[email protected]>

from socket import htonl, htons, ntohs, ntohl
import p_obex
import prototypes
import common
import cStringIO
import time
from array import array
from xml.dom.minidom import parseString
from sys import exc_info

"""
OBEX Protocol handler, used to transfer files to and from the phone.
This does not necessarily mean Bluetooth as OBEX is a generic protocol
over many transports, including IrDA, TCP/IP, RFCOMM, Serial, etc
"""

# OBEX headers
OBEX_HDR_EMPTY       = 0x00
OBEX_HDR_COUNT       = 0xc0
OBEX_HDR_NAME        = 0x01
OBEX_HDR_TYPE        = 0x42
OBEX_HDR_TIME        = 0x44
OBEX_HDR_TIME2       = 0xC4
OBEX_HDR_LENGTH      = 0xc3
OBEX_HDR_DESCRIPTION = 0x05
OBEX_HDR_TARGET      = 0x46
OBEX_HDR_BODY        = 0x48
OBEX_HDR_BODY_END    = 0x49
OBEX_HDR_WHO         = 0x4a
OBEX_HDR_APPARAM     = 0x4c
OBEX_HDR_AUTHCHAL    = 0x4d
OBEX_HDR_AUTHRESP    = 0x4e
OBEX_HDR_OBJCLASS    = 0x4f
OBEX_HDR_CONNECTION  = 0xcb

# OBEX header types
OBEX_HI_MASK     = 0xc0
OBEX_UNICODE     = 0x00
OBEX_BYTE_STREAM = 0x40
OBEX_BYTE        = 0x80
OBEX_INT         = 0xc0

OBEX_FINAL = 0x80
OBEX_RSP_SUCCESS  = 0x20
OBEX_RSP_CONTINUE = 0x10

# We seem to start losing data with higher values.
OBEX_MTU = 1024

OBEX_LISTING_TYPE = "x-obex/folder-listing"
OBEX_RAW_HEADERS_EMPTY = "no value has been initialized for this field yet"

class ObexCommandException(Exception):
    def __init__(self, str="OBEX Command Exception"):
        Exception.__init__(self, str)

class ObexConnectException(ObexCommandException):
    def __init__(self, str):
        ObexCommandException.__init__(self, str)
class ObexIncompletePacketException(ObexCommandException):
    def __init__(self,str):
        ObexCommandException.__init__(self, str)

class ObexDisconnectException(ObexCommandException):
    def __init__(self, str):
        ObexCommandException.__init__(self, str)

class ObexProtocol:
    OBEX_FTP_TARGET = ( 0xf9, 0xec, 0x7b, 0xc4, 0x95, 0x3c, 0x11, 0xd2, \
                        0x98, 0x4e, 0x52, 0x54, 0x00, 0xdc, 0x9e, 0x09 )
    MODEOBEX = "modeobex"
    _mtu = -1
    # hopefully there should not be any concurrency issue storing this as
    # a member.  0x0 seems to be a reasonable default.
    _connId = 0x0 
    _goodrate = 0
    _connected = False
    def __init__(self):
        self._connected = False

    def obexconnect(self):
        obex_ftp_target = array('B', self.OBEX_FTP_TARGET)
        req = p_obex.obex_connect_request()
        targetstr = obex_ftp_target.tostring()
        req.target.data = prototypes.DATA(**{ 'value': targetstr })
        resp = self.sendobexcommand(req, p_obex.obex_connect_response)
        self.log("response code = 0x%x" % resp.response)
        if OBEX_FINAL | OBEX_RSP_SUCCESS != resp.response:
            raise ObexConnectionException(
                "Expected response 0xA0, got 0x%x" % resp.response)
        self.log("obex version = 0x%x" % resp.obexVersion)
        self._mtu = ntohs(resp.maxLength)
        self.log("max packet length = %d" % self._mtu)
        for i in resp.headers:
            if i.headerId == OBEX_HDR_CONNECTION:
                self.connId = i.headerData
                self.log("Found connection ID: 0x%x" % self.connId)
        self.log("Successfully connected to OBEX FTP server")
        self._connected = True

    def obexdisconnect(self):
        if not self._connected:
            self.log("Already disconnected, ignoring OBEX disconnect request.")
            return
        req = p_obex.obex_disconnect_request()
        req.connId.data = prototypes.DATA(**{ 'value': self.connId })
        resp = self.sendobexcommand(req, p_obex.obex_disconnect_response)
        if OBEX_FINAL | OBEX_RSP_SUCCESS != resp.response:
            raise ObexDisconnectionException(
                "Expected response 0xA0, got 0x%x" % resp.response)
        self.log("Successfully disconnected from OBEX FTP server")
        self.connId = -1
        self._connected = False

    def createheader(self, htype, data):
        header = p_obex.obex_header(**{ 'headerId': htype })
        header.data = prototypes.DATA(**{ 'value': data })
        return header

    # pass in an empty name for root
    def setobexfolder(self, name):
        req = p_obex.obex_setpath_request()
        req.headers = self.newheaderlist()
        req.flags = 0x02 # go forward
        req.headers.append(self.createheader(OBEX_HDR_CONNECTION, self.connId))
        req.headers.append(self.createheader(OBEX_HDR_NAME, name))
        resp = self.sendobexcommand(req, p_obex.obex_setpath_response)
        if resp.response != OBEX_FINAL | OBEX_RSP_SUCCESS:
            raise ObexCommandException(
                "Expected 0xA0, got 0x%x" % resp.response)

    def newheaderlist(self):
        return prototypes.LIST(**{ 'elementclass': p_obex.obex_header })

    # returns a string containing the contents of the file
    def readobexfile(self, req):
        resp = self.sendobexcommand(req, p_obex.obex_get_file_response)
        req = p_obex.obex_get_file_request()
        req.headers = self.newheaderlist()
        b = cStringIO.StringIO()

        while True:
            for i in resp.headers:
                if i.headerId in (OBEX_HDR_BODY, OBEX_HDR_BODY_END):
                    b.write(i.headerData)
            if resp.response == OBEX_FINAL | OBEX_RSP_SUCCESS:
                self.log("Successfully transferred")
                break
            elif resp.response == OBEX_FINAL | OBEX_RSP_CONTINUE:
                resp = self.sendobexcommand(req, p_obex.obex_get_file_response)
            else:
                raise ObexCommandException(
                    "Unknown response: 0x%x" % resp.response)
        return b.getvalue()
        
    # returns a string containing the contents of the file
    def getobexfile(self, directory, filename):
        self.setobexfolder("")
        self.setobexfolder(directory)
        req = p_obex.obex_get_file_request()
        req.headers = self.newheaderlist()
        req.headers.append(self.createheader(OBEX_HDR_CONNECTION, self.connId))
        req.headers.append(self.createheader(OBEX_HDR_NAME, filename))
        return self.readobexfile(req)
        
    def getobexfolderlist(self, name=None):
        self.setobexfolder("")
        if name is not None:
            self.setobexfolder(name)
        req = p_obex.obex_get_file_request()
        req.headers = self.newheaderlist()
        req.headers.append(self.createheader(OBEX_HDR_CONNECTION, self.connId))
        req.headers.append(self.createheader(OBEX_HDR_TYPE, OBEX_LISTING_TYPE))
        xmlstr = self.readobexfile(req)
        folderDom = parseString(xmlstr)
        files = folderDom.getElementsByTagName("file")
        fileList = []
        for i in files:
            n = i.getAttribute("name")
            t = i.getAttribute("type")
            self.log("Folder contents: %s - %s" % (n, t))
            fileList.append(n)
            
        folderDom.unlink()
        return fileList

    def sendobexcommand(self, req, responsetype):
        buff = prototypes.buffer()
        req.packetLength = 1
        size = req.packetsize()
        req.packetLength = htons(size)
        req.writetobuffer(buff, logtitle="OBEX request")
        data = buff.getvalue()
        self.comm.write(data)
        return self.readobexresponse(responsetype)

    # read the first 3 bytes of an obex packet
    def readobexpacketheader(self):
        resp = p_obex.obex_response()
        r = self.comm.read(numchars=3)
        if len(r) == 0:
            raise ObexCommandException("No response to OBEX command")
        b = prototypes.buffer(r)
        resp.readfrombuffer(b)
        resp.packetLength = ntohs(resp.packetLength)
        return resp

    def readobexresponse(self, responsetype):
        r = self.readobexpacketheader()

        resp = responsetype()
        resp.response = r.response
        resp.packetLength = r.packetLength
        length = resp.packetLength

        length -= 3
        if length > 0:
            remain = length
            cbuf = cStringIO.StringIO()
            readAttempts = 0
            while remain > 0 and readAttempts < 3:
                rd = self.comm.read(numchars=remain)
                remain -= len(rd)
                cbuf.write(rd)
                readAttempts += 1
                if remain > 0:
                    # this seems to be a problem on my V3c
                    # can't read anymore data without doing a reset
                    # or sending another packet, the former means
                    # we will lose the rest of this packet, while
                    # the latter means we will be completely out of
                    # sync with the OBEX server.  We must perform a
                    # port reset.
                    self.log("WARNING: can't read entire packet, data lost!")
                    self.comm.reset()
                    self.comm.setbaudrate(self._goodrate)
            packet = cbuf.getvalue()

            bytesread = len(packet)
            if bytesread != length:
                self.log("WARNING: bytes read != packet length")
                self.log("WARNING: consider lowering OBEX_MTU")
                resp.packetLength = bytesread
                
            b = prototypes.buffer(packet)
            resp.readfrombuffer(b)

            if resp.rawHeaders is not OBEX_RAW_HEADERS_EMPTY:
                headerstream = cStringIO.StringIO(resp.rawHeaders)
                while True:
                    header = self.readobexheader(headerstream)
                    if header is None:
                        break
                    resp.headers.append(header)
        return resp

    def readobexheader(self, stream):
        headerId = stream.read(1)
        if len(headerId) == 0:
            return None
        headerId = self.readuint8(headerId)
        header = p_obex.obex_response_header()
        header.headerId = headerId

        if headerId & OBEX_HI_MASK == OBEX_UNICODE:
            # -3 to accomodate for headerId uint8 and packetsize word
            headerSize = self.readuint16(stream.read(2)) - 3
            data = stream.read(headerSize)
            stringheader = p_obex.obex_string_header()
            stringheader.readfrombuffer(prototypes.buffer(data))
            headerData = stringheader.data

        if headerId & OBEX_HI_MASK == OBEX_BYTE_STREAM:
            # -3 to accomodate for headerId uint8 and packetsize word
            headerSize = self.readuint16(stream.read(2)) - 3
            streamheader = p_obex.obex_stream_header()
            data = stream.read(headerSize)
            streamheader.readfrombuffer(prototypes.buffer(data))
            headerData = streamheader.data

        if headerId & OBEX_HI_MASK == OBEX_BYTE:
            headerData = self.readuint8(stream.read(1))

        if headerId & OBEX_HI_MASK == OBEX_INT:
            headerData = self.readuint32(stream.read(4))
        header.data = prototypes.DATA(**{ 'value': headerData })
        header.packetsize()
        return header
        
    def readuint8(self, data):
        r = p_obex.obex_uint8()
        r.readfrombuffer(prototypes.buffer(data))
        return r.data

    def readuint32(self, data):
        r = p_obex.obex_int_header()
        r.readfrombuffer(prototypes.buffer(data))
        return r.data

    def readuint16(self, data):
        ui16 = p_obex.obex_uint16()
        ui16.readfrombuffer(prototypes.buffer(data))
        return ntohs(ui16.data)
p_obex.p (text/plain, 6.1 KB)
###
### BITPIM
###
### Author: Perry Nguyen <[email protected]>

%{

"""OBEX protocol definitions"""

from prototypes import *
from socket import htonl, htons, ntohs, ntohl

# OBEX uses MSB bitpim doesn't seem to have it, so we'll convert ourselves.
# I suck at python and don't know how to write a UINTmsb...
UINT=UINTlsb

# Constants copied from OpenOBEX
# OBEX commands
OBEX_CMD_CONNECT    = 0x00
OBEX_CMD_DISCONNECT = 0x01
OBEX_CMD_PUT        = 0x02
OBEX_CMD_GET        = 0x03
OBEX_CMD_COMMAND    = 0x04
OBEX_CMD_SETPATH    = 0x05
OBEX_CMD_ABORT      = 0x7f
OBEX_FINAL          = 0x80

# OBEX version 1.1
OBEX_VERSION        = 0x10

# OBEX File Browsing UUID = F9EC7BC4-953C-11D2-984E-525400DC9E09

import com_obex

%}

PACKET obex_header:
    P   DATA data "the actual header data"
    1   UINT headerId "one of the OBEX_HDR types"
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_UNICODE:
        # additional +1 to account for terminator
        # utf_16be = big endian, seems obex goes by this?
        2 UINT { 'value': htons(len(self.data.encode("utf_16be")) + 4) } +headerSize
        * USTRING { 'encoding': "utf_16be", 'value': self.data } +headerData
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_BYTE_STREAM:
        2 UINT { 'value': htons(len(self.data) + 3) } +headerSize
        * DATA { 'value': self.data } +headerData
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_BYTE:
        1 UINT { 'value': self.data } +headerData
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_INT:
        4 UINT { 'value': htonl(self.data) } +headerData

PACKET obex_response_header:
    P   UINT headerId "one of the OBEX_HDR types"
    P   DATA data "the actual header data"
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_UNICODE:
        * USTRING { 'value': self.data } +headerData
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_BYTE_STREAM:
        * DATA { 'value': self.data } +headerData
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_BYTE:
        1 UINT { 'value': self.data } +headerData
    if self.headerId & com_obex.OBEX_HI_MASK == com_obex.OBEX_INT:
        4 UINT { 'value': self.data } +headerData "in MSB first"

PACKET obex_response:
    1 UINT response
    2 UINT packetLength

PACKET obex_uint8:
    1 UINT data
    
PACKET obex_uint16:
    2 UINT data

PACKET obex_byte_header:
    1 UINT data
PACKET obex_int_header:
    4 UINT data
PACKET obex_string_header:
    * USTRING data
PACKET obex_stream_header:
    * DATA data

PACKET obex_connect_request:
    1 UINT { 'value': (OBEX_CMD_CONNECT | OBEX_FINAL) } +connectop
    2 UINT packetLength
    1 UINT { 'constant': OBEX_VERSION } +obexVersion
    1 UINT { 'value': 0x00 } +flags
    2 UINT { 'value': htons(com_obex.OBEX_MTU) } +maxLength
    * obex_header { 'headerId': com_obex.OBEX_HDR_TARGET } +target

PACKET obex_connect_response:
    P UINT response "0x41 = unauthorized, 0xA0 = success"
    P UINT packetLength
    1 UINT obexVersion
    1 UINT flags
    2 UINT maxLength "in MSB first"
    * DATA { 'sizeinbytes': self.packetLength - 7 } +rawHeaders
    P LIST { 'elementclass': obex_response_header } headers

PACKET obex_disconnect_request:
    1 UINT { 'value': OBEX_CMD_DISCONNECT | OBEX_FINAL } +disconnectop
    2 UINT packetLength
    * obex_header { 'headerId': com_obex.OBEX_HDR_CONNECTION } +connId

PACKET obex_disconnect_response:
    P UINT response "must be 0xA0"
    P UINT packetLength
    * DATA { 'sizeinbytes': self.packetLength - 3, 'default': com_obex.OBEX_RAW_HEADERS_EMPTY } +rawHeaders
    P LIST { 'elementclass': obex_response_header } headers

PACKET obex_setpath_request:
    1 UINT { 'value': OBEX_CMD_SETPATH | OBEX_FINAL } +setpathop
    2 UINT packetLength
    1 UINT flags "go up = 0x03, go forward = 0x02"
    1 UINT { 'value': 0x00 } +setpathconstants
    * LIST { 'elementclass': obex_header } headers
    # * obex_header { 'headerId': com_obex.OBEX_HDR_CONNECTION } +connId
    # * obex_header { 'headerId': com_obex.OBEX_HDR_NAME } +folderName "no folder name if go up, empty if root"

PACKET obex_setpath_response:
    P UINT response "0xA0 = success, 0xC4 = not exist or already at root, 0xC1 = not permitted"
    P UINT packetLength
    * DATA { 'sizeinbytes': self.packetLength - 3, 'default': com_obex.OBEX_RAW_HEADERS_EMPTY } +rawHeaders
    P LIST { 'elementclass': obex_response_header } headers

PACKET obex_delete_file_request:
    1 UINT { 'value': OBEX_CMD_PUT | OBEX_FINAL } +putop
    2 UINT packetLength
    * obex_header { 'headerId': com_obex.OBEX_HDR_CONNECTION } +connId
    * obex_header { 'headerId': com_obex.OBEX_HDR_NAME } +name "name to delete"

PACKET obex_delete_file_response:
    P UINT response "0xA0 = success, 0xC1 unauthorized, 0xC4 not exist"
    P UINT packetLength
    * DATA { 'sizeinbytes': self.packetLength - 3 } +responseData

PACKET obex_get_file_request:
    1 UINT { 'value': OBEX_CMD_GET | OBEX_FINAL } +getop
    2 UINT packetLength
    * LIST { 'elementclass': obex_header } headers

    # * obex_header { 'headerId': OBEX_HDR_CONNECTION } +connId
    # * obex_header { 'headerId': OBEX_HDR_TYPE } +objType
    # * obex_header { 'headerId': OBEX_HDR_NAME } +name

PACKET obex_get_file_response:
    P UINT response "0x90 = continue, 0xA0 = done"
    P UINT packetLength "in MSB first"
    * DATA { 'sizeinbytes': self.packetLength - 3 } +rawHeaders
    P LIST { 'elementclass': obex_response_header } headers

PACKET obex_put_file_request:
    1 UINT { 'value': OBEX_CMD_PUT } +putop
    2 UINT packetLength
    * obex_header { 'headerId': com_obex.OBEX_HDR_CONNECTION } +connId
    * obex_header { 'headerId': com_obex.OBEX_HDR_NAME } +name
    * obex_header { 'headerId': com_obex.OBEX_HDR_BODY } +body

PACKET obex_put_file_end_request:
    1 UINT { 'value': OBEX_CMD_PUT | OBEX_FINAL } +putop
    2 UINT packetLength
    * obex_header { 'headerId': com_obex.OBEX_HDR_CONNECTION } +connId
    * obex_header { 'headerId': com_obex.OBEX_HDR_NAME } +name
    * obex_header { 'headerId': com_obex.OBEX_HDR_BODY_END } +body

PACKET obex_put_file_response:
    P UINT  response "0x90 = continue, 0xA0 = success"
    P UINT packetLength
    * DATA { 'sizeinbytes': self.packetLength - 3 } +responseData
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.