Closed cefn closed 11 years ago
There are actually two types of handlers, which is what causes the confusion I think. There are handlers for 'normal' commands, like "report analog pin", "report digital port" and "report version". And there are also handlers for sysex-commands, like "report firmware". The difference is that the former take a specified number of arguments (usually two, a least and most significant byte, to be able to define numbers higher than 256) and the latter take any number of arguments, as everything is read until the END_SYSEX bytes comes along. See the iterate method for how this works.
So if you want to define a non-sysex handler, use the args you need. If you define a handler for a sysex command (which you probably want to do for custom commands), you can do the same if the number of arguments is fixed, or use *data
as in the _handle_report_firmware
handler.
Looking at it now, I might split the handler in two types later (sysex/non-sysex) as that looks a little clearer.
About moving towards 2.2/2.3, I think the only thing really needed is implementing the remaining sysex commands for capabilities querying and the like. But it might include some refactoring to make this easier. I will probably look at this next week.
Thanks for taking the time for such a detailed reply. This information should be more than enough for me to put together my code examples.
In particular I'm aiming to write logic on both sides of the connection which permits the marshalling and unmarshalling of canonical Arduino data types (based on 8-bit), into a 7-bit transport, as per the code below in Arduino, and the equivalent code in python below that. Please note these code blocks have not been tested, but in some ways code is its own best description.
/** Symmetric with decodefrom7 - encodes bytes to 7-bit values, with an overflow byte
* to store extra bits. Returns the number of total bytes written to the destination byte array*/
int encodeTo7(byte* srcBytes, byte* dstBytes, int srcCount){
byte overflowByte = 0;
byte overflowPos = 0;
int dstPos = 0; //current writing position in dst array
for(int srcPos = 0; srcPos < srcCount; srcPos++){
//store msb in overflowByte, filling bits from left, shifting one extra to avoid msb
overflowByte |= (srcBytes[srcPos] & 0x80) >> (overflowPos + 1); //store msb in overflow byte using next free position
overflowPos++; //update overflow write position
//passthrough the 7-bit truncated byte filling destination array from left
dstBytes[dstPos] = srcBytes[srcPos] & ~0x80; //write the 7 allowed bits (zeroing the msb)
dstPos++;//update destination write position
//write overflow byte to stream when it's full or stream is ending
if(overflowPos == 7 || srcPos == srcCount-1){ //byte full or stream ended
dstBytes[dstPos] = overflowByte; //write the 7 allowed bits
dstPos++; //update destination write position
overflowByte = 0; //reset overflow byte
overflowPos = 0; //start writing from beginning again
}
}
return dstPos;
}
/** Should be symmetric with encodeTo7(). It populates the specified number of output bytes by reading
* 7 bits from each source byte and retrieving the extra bit from a 7-bit overflow byte which is written after
* after each frame of 7 bytes (with the final frame potentially being less than 7 bytes). */
void decodeFrom7(byte* srcBytes, byte* dstBytes, int dstCount){
int srcPos = 0;
int partialFrameLength = dstCount % 7; //find out number of bytes stored in last frame (remainder from frames of 7)
//int srcCount = ((dstCount / 7) * 8) + (partialFrameLength == 0 ? 0 : partialFrameLength + 1); //count of source bytes needed
int overflowPos = 0; //keeps track of the next overflow bit to read from the overflow byte
for(int dstPos = 0; dstPos < dstCount; dstPos++){
//get the 7 bits from the current byte, and reconstruct the final bit from the overflowbyte
int frameLength = dstPos + partialFrameLength < dstCount ? 7 : partialFrameLength; //frame length (not including overflow byte)
dstBytes[dstPos] = srcBytes[srcPos] | ((srcBytes[srcPos - overflowPos + frameLength] << (1 + overflowPos)) | 0x80); //read extra overflow bit from predictably offset overflow byte
//(srcPos - overflowPos is the start of the frame)
overflowPos++; //update overflow read position
srcPos++; //update src read position
if(overflowPos == 7){
overflowPos = 0;
srcPos++; //update read position to skip the overflow byte
}
}
}
# returns the bytes encoded see canonical Arduino code for comments - this is a python port
def encode7(srcbytes):
dstbytes = array()
overflowbyte = 0
overflowpos = 0
dstpos = 0
for(srcpos, srcbyte in enumerate(srcbytes):
overflowbyte |= (srcvalues[srcpos] & 0x80) >> (overflowpos + 1)
overflowpos += 1
dstbytes.push(srcbytes[srcpos] & ~0x80)
dstpos += 1
if(overflowPos == 7 || srcPos == srcCount-1):
dstbytes.push(overflowbyte);
dstpos += 1
overflowbyte = 0
overflowpos = 0
return dstbytes
# returns the bytes decoded, see canonical Arduino code for comments - this is a port
def decode7(srcbytes, dstcount):
dstbytes = array()
srcpos = 0
partialframelength = srcbytes.length mod 8
if(partialframelength != 0):
partialframelength -= 1
overflowpos = 0
for(dstpos in range(dstcount)):
framelength = 7 if (dstpos + partialframelength < dstcount) else partialframelength
dstbytes.push(srcbytes[srcpos] | ((srcbytes[srcpos - overflowpos + framelength] << (1 + overflowpos)) | 0x80))
overflowpos += 1
srcpos += 1
if(overflowpos == 7):
overflowpos = 0
srcpos += 1
return dstbytes
I think I understand the way that handlers are bound to Firmata events (via add_cmd_handler) but I'm flummoxed how to tell what kind of structure my handler function should have so that introspection serves out arguments correctly for a new command binding (beyond the analog and digital bindings you've already provided).
Are there examples or documentation I've missed, for example, detailing how to add a handler for an arbitrary string command, as sent from the Firmata function "sendString" ? If not, would it be possible to create an example or a note about how add_cmd_handler works/might work for the range of possible transmissions listed at... http://www.firmata.org/wiki/V2.2ProtocolDetails ...or even... http://www.firmata.org/wiki/V2.3ProtocolDetails
Is it simply about defining a function with a single named/unnamed byte argument per byte expected beyond the command byte, then unmarshalling them in ways which reflect the protocol?
I'm guessing that moving towards 2.2/2.3 is mainly about this kind of binding, but I don't know if there's a structural problem with the current code passing through the newer messages since 2.1. This might be another area which I can get to in coming months.