Logitech Craft is almost mapped, but I need help with fine scrolling.

With the help of the generic devices tool, I have mapped the basic functionality of the Logitech Craft keyboard, with the rotating dial. I am also using it blissfully free of the Crown Overlay, on a Mac.

The Craft has two rotation modes, a ratcheted click step one and a superfine smooth one.

The click step one was easy to map and get usable triggers for, but the smooth one is more involved.

For the coarse step, it has Index 6 which has "01" for clockwise Crown movement and "ff" for counter-clockwise rotation.

But for the fine movement reporting it is index 18+19 and it has six rotation speeds being reported for each direction! "ff", "fe", "fd", "fc", etc., and 01-(maybe?)07 for the other direction.

When I try to use those indexes, I am not getting buttery smooth scrolling. What is happening is that Index 18 has the speed range in multiple values, and 19 has the events, and I don't know how to capture it.

<code


function analyzeDeviceInput(targetDevice, reportID, reportDataHex) {

let reportBuffer = buffer.Buffer.from(reportDataHex, 'hex');
// the values you see above are in hex format. To read such a byte
// use readUInt8(index).

//Touch events

if(reportBuffer.readUInt8(8) === 0x03 && reportBuffer.readUInt8(9) === 0x01) {
    log('doubletouch');
    bttTriggerDeviceTrigger(targetDevice, 'DoubleTouch')
    }

if(reportBuffer.readUInt8(8) === 0x01) {
    log('touch');
    bttTriggerDeviceTrigger(targetDevice, 'Touch');
}else if(reportBuffer.readUInt8(8) === 0x03) {
    log('touchrelease');
    bttTriggerDeviceTrigger(targetDevice, 'TouchRelease');
}

 //Press events
if(reportBuffer.readUInt8(10) === 0x01) {
    log('press');
    bttTriggerDeviceTrigger(targetDevice, 'Press');
}else if(reportBuffer.readUInt8(10) === 0x03) {
    log('pressholdstatic');
    bttTriggerDeviceTrigger(targetDevice, 'PressHoldStatic');
}else if(reportBuffer.readUInt8(10) === 0x05) {
    log('pressrelease');
    bttTriggerDeviceTrigger(targetDevice, 'PressRelease');
}

//StepEvents

    if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0x01 && reportBuffer.readUInt8(10) === 0x00) {
    log('stepcw');
    bttTriggerDeviceTrigger(targetDevice, 'StepCW');
}else if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0xff && reportBuffer.readUInt8(10) === 0x00) {
    log('stepccw');
    bttTriggerDeviceTrigger(targetDevice, 'StepCCW');
}
    if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0x01 && reportBuffer.readUInt8(10) === 0x04) {
    log('pressstepcw');
    bttTriggerDeviceTrigger(targetDevice, 'PressStepCW');
}else if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0xff && reportBuffer.readUInt8(10) === 0x04) {
    log('pressstepccw');
    bttTriggerDeviceTrigger(targetDevice, 'PressStepCCW');
}

Here are my notes on the index for anyone interested in more detail:

It has an index report of 19 fields.
The ones that seem significant for BTT are 4,5,6,8,9,10,18,19

Index 4 is a simple indicator of whether any movement is happening. It does not give direction information.

  • 00 Still
  • 01 The tiniest first step of movement (this event could be mapped for delicate super-fine nudges)
  • 02 For the rest of the time the Crown is being rotated

Index 5 gives the reported speed of user rotation, on a scale from ff to at least fc for counterclockwise and 01 to at least 05.

Index 6 reports each full ratcheted step with either a 01 for a clockwise step, and an ff for a counter-clockwise step. If all a person wanted to do was catch the coarse steps this and probably 4 are all you need.

Index 8 is whether the Crown is being touched at all:

  • 00 No contact
  • 01 Crown touched without rotation,
  • 02 Crown touched and actively rotating,
  • 03 When contact is broken it can give a second of 03 before going back to 00

Index 9 triggers when you give the crown a double tap touch (not pressing, but a light double touch as on a track pad). This one is amazing, and makes for a great play/pause trigger.

Index 10 is whether the Crown is being held down

  • 00 no
  • 04 yes

Index 18 is similar to index 5, (ff-±fc and 01-±05) but they are not always giving the same report value. If I spin the Crown for a longer time, it starts registering as 00 which means it would fail as a trigger?

Index 19 is giving event information on each movement, but I don’t know if it is giving delta degrees out of the whole circle based on the speed of the rotation, or if it is just reporting that a movement happened.

I think this is where the performance is, but I don’t know how to capture it with BTT.

Update: I'm using the Powermate sample and it's working. Index 19 is "movement" in the same way as the movement for the Powermate.

1 Like

That Powermate code did the trick!

I had to use a different reference for rotation direction, because Index 19 was throwing positives and negatives. It probably will need a little refinement, but for now, this code is doing what I need, with a coarse step scroll, a fine step scroll, and a double-touch trigger. I still want to add something for the super-fine control nudge-movement, but I'll try this for a while.

Touch
TouchRelease
DoubleTouch
Press
PressHoldStatic
PressRelease
StepCW
StepCCW
PressStepCW
PressStepCCW
FineCW
FineCCW
PressFineCW
PressFineCCW

// Enter your input analyzer script here. 
// Do not change the function signatures
function analyzeDeviceInput(targetDevice, reportID, reportDataHex) {      
let reportBuffer = buffer.Buffer.from(reportDataHex, 'hex');
// the values you see above are in hex format. To read such a byte
// use readUInt8(index).
// Index 0 is crown press state. 1 = press, 0 = release.
    crownpress = reportBuffer.readUInt8(10),
    // Index 1 is amount of movement since last report.
    // Signed value, positive for right, negative for left.
    // Spinning really fast I have observed a value up to +/- 7,
    // lets assume it will never be larger than 9.
    movement = reportBuffer.readInt8(19),
    direction = reportBuffer.readInt8(5)
    ratchetstep = reportBuffer.readInt8(6),
    touch = reportBuffer.readInt8(8),
    doubletouch = reportBuffer.readInt8(9);



if ( direction < 0 ) {
    if ( crownpress == 04 ) {
        log('fineccw ' + movement  + ' pressed');
        // We should probably repeat the trigger "movement" times ...
        // but that would be too many triggers :)
        bttTriggerDeviceTrigger(targetDevice, 'PressFineCCW');
    } else if ( crownpress == 00  && touch == 02) {
        log('fineccw ' + movement);
        bttTriggerDeviceTrigger(targetDevice, 'FineCCW');
    }
    // If we are spinning we want the next report even if it's the same
    bttGetNextEvenWithoutChange(targetDevice, reportID);
}
else if( direction > 0 ) {
    if ( crownpress == 04 ) {
        log('finecw ' + movement + ' pressed');
        bttTriggerDeviceTrigger(targetDevice, 'PressFineCW');
    } else if ( crownpress == 00  && touch == 02){
        log('finecw ' + movement);
        bttTriggerDeviceTrigger(targetDevice, 'FineCW');
    }
    bttGetNextEvenWithoutChange(targetDevice, reportID);
}

//Touch events
if ( touch == 03 && doubletouch == 01 ) {
        log('doubletouch');
        // Tap the crown twice without pushing it.
        bttTriggerDeviceTrigger(targetDevice, 'DoubleTouch');
        }

if(reportBuffer.readUInt8(8) === 0x01) {
    log('touch');
    bttTriggerDeviceTrigger(targetDevice, 'Touch');
}else if(reportBuffer.readUInt8(8) === 0x03) {
    log('touchrelease');
    bttTriggerDeviceTrigger(targetDevice, 'TouchRelease');
}

 //Press events
if(reportBuffer.readUInt8(10) === 0x01) {
    log('press');
    bttTriggerDeviceTrigger(targetDevice, 'Press');
}else if(reportBuffer.readUInt8(10) === 0x03) {
    log('pressholdstatic');
    bttTriggerDeviceTrigger(targetDevice, 'PressHoldStatic');
}else if(reportBuffer.readUInt8(10) === 0x05) {
    log('pressrelease');
    bttTriggerDeviceTrigger(targetDevice, 'PressRelease');
}

//StepEvents

    if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0x01 && reportBuffer.readUInt8(10) === 0x00) {
    log('stepcw');
    bttTriggerDeviceTrigger(targetDevice, 'StepCW');
}else if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0xff && reportBuffer.readUInt8(10) === 0x00) {
    log('stepccw');
    bttTriggerDeviceTrigger(targetDevice, 'StepCCW');
}
    if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0x01 && reportBuffer.readUInt8(10) === 0x04) {
    log('pressstepcw');
    bttTriggerDeviceTrigger(targetDevice, 'PressStepCW');
}else if(reportBuffer.readUInt8(8) === 2 && reportBuffer.readUInt8(6) === 0xff && reportBuffer.readUInt8(10) === 0x04) {
    log('pressstepccw');
    bttTriggerDeviceTrigger(targetDevice, 'PressStepCCW');
}
1 Like

Very interesting, thanks for sharing!

1 Like