For background, the word “Program” in this context is a pre-defined RPM that can be run by a remote controller, nothing more.

We’ll start by brute forcing ACTION == 0x02 with every possible 2-byte argument and throw away the known errors. This gets us quite a lot of raw data:

hex(Req Action) | Req Data | hex(Req Data) | hex(Res Action) | Res Data | hex(Res Data) | sum(Res Data)
0x2 | [0, 100] | ['0x0', '0x64'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [0, 103] | ['0x0', '0x67'] | 0x2 | [8, 53] | ['0x8', '0x35'] | 2101
0x2 | [0, 104] | ['0x0', '0x68'] | 0x2 | [0, 175] | ['0x0', '0xaf'] | 175
0x2 | [0, 108] | ['0x0', '0x6c'] | 0x2 | [1, 244] | ['0x1', '0xf4'] | 500
0x2 | [0, 112] | ['0x0', '0x70'] | 0x2 | [7, 88] | ['0x7', '0x58'] | 1880
0x2 | [0, 201] | ['0x0', '0xc9'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [0, 202] | ['0x0', '0xca'] | 0x2 | [7, 208] | ['0x7', '0xd0'] | 2000
0x2 | [0, 205] | ['0x0', '0xcd'] | 0x2 | [7, 208] | ['0x7', '0xd0'] | 2000
0x2 | [0, 207] | ['0x0', '0xcf'] | 0x2 | [0, 20] | ['0x0', '0x14'] | 20
0x2 | [0, 208] | ['0x0', '0xd0'] | 0x2 | [0, 20] | ['0x0', '0x14'] | 20
0x2 | [0, 240] | ['0x0', '0xf0'] | 0x2 | [0, 5] | ['0x0', '0x5'] | 5
0x2 | [1, 154] | ['0x1', '0x9a'] | 0x2 | [0, 12] | ['0x0', '0xc'] | 12
0x2 | [1, 155] | ['0x1', '0x9b'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
0x2 | [1, 156] | ['0x1', '0x9c'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [1, 160] | ['0x1', '0xa0'] | 0x2 | [15, 160] | ['0xf', '0xa0'] | 4000
0x2 | [1, 164] | ['0x1', '0xa4'] | 0x2 | [0, 50] | ['0x0', '0x32'] | 50
0x2 | [1, 165] | ['0x1', '0xa5'] | 0x2 | [8, 52] | ['0x8', '0x34'] | 2100
0x2 | [1, 166] | ['0x1', '0xa6'] | 0x2 | [0, 50] | ['0x0', '0x32'] | 50
0x2 | [1, 167] | ['0x1', '0xa7'] | 0x2 | [0, 111] | ['0x0', '0x6f'] | 111
0x2 | [1, 168] | ['0x1', '0xa8'] | 0x2 | [0, 100] | ['0x0', '0x64'] | 100
0x2 | [1, 169] | ['0x1', '0xa9'] | 0x2 | [0, 222] | ['0x0', '0xde'] | 222
0x2 | [1, 170] | ['0x1', '0xaa'] | 0x2 | [0, 200] | ['0x0', '0xc8'] | 200
0x2 | [1, 171] | ['0x1', '0xab'] | 0x2 | [1, 78] | ['0x1', '0x4e'] | 334
0x2 | [1, 172] | ['0x1', '0xac'] | 0x2 | [1, 44] | ['0x1', '0x2c'] | 300
0x2 | [1, 173] | ['0x1', '0xad'] | 0x2 | [1, 189] | ['0x1', '0xbd'] | 445
0x2 | [1, 174] | ['0x1', '0xae'] | 0x2 | [1, 144] | ['0x1', '0x90'] | 400
0x2 | [1, 175] | ['0x1', '0xaf'] | 0x2 | [2, 45] | ['0x2', '0x2d'] | 557
0x2 | [1, 176] | ['0x1', '0xb0'] | 0x2 | [1, 244] | ['0x1', '0xf4'] | 500
0x2 | [1, 177] | ['0x1', '0xb1'] | 0x2 | [2, 156] | ['0x2', '0x9c'] | 668
0x2 | [1, 178] | ['0x1', '0xb2'] | 0x2 | [2, 88] | ['0x2', '0x58'] | 600
0x2 | [1, 179] | ['0x1', '0xb3'] | 0x2 | [3, 123] | ['0x3', '0x7b'] | 891
0x2 | [1, 180] | ['0x1', '0xb4'] | 0x2 | [3, 32] | ['0x3', '0x20'] | 800
0x2 | [1, 181] | ['0x1', '0xb5'] | 0x2 | [4, 90] | ['0x4', '0x5a'] | 1114
0x2 | [1, 182] | ['0x1', '0xb6'] | 0x2 | [3, 232] | ['0x3', '0xe8'] | 1000
0x2 | [1, 183] | ['0x1', '0xb7'] | 0x2 | [5, 57] | ['0x5', '0x39'] | 1337
0x2 | [1, 184] | ['0x1', '0xb8'] | 0x2 | [4, 176] | ['0x4', '0xb0'] | 1200
0x2 | [1, 185] | ['0x1', '0xb9'] | 0x2 | [7, 158] | ['0x7', '0x9e'] | 1950
0x2 | [1, 186] | ['0x1', '0xba'] | 0x2 | [6, 214] | ['0x6', '0xd6'] | 1750
0x2 | [1, 194] | ['0x1', '0xc2'] | 0x2 | [30, 220] | ['0x1e', '0xdc'] | 7900
0x2 | [1, 195] | ['0x1', '0xc3'] | 0x2 | [0, 237] | ['0x0', '0xed'] | 237
0x2 | [1, 196] | ['0x1', '0xc4'] | 0x2 | [0, 100] | ['0x0', '0x64'] | 100
0x2 | [1, 214] | ['0x1', '0xd6'] | 0x2 | [7, 208] | ['0x7', '0xd0'] | 2000
0x2 | [1, 215] | ['0x1', '0xd7'] | 0x2 | [3, 232] | ['0x3', '0xe8'] | 1000
0x2 | [1, 245] | ['0x1', '0xf5'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [217, 64] | ['0xd9', '0x40'] | 55616
0x2 | [2, 6] | ['0x2', '0x6'] | 0x2 | [7, 208] | ['0x7', '0xd0'] | 2000
0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [1, 239] | ['0x1', '0xef'] | 495
0x2 | [2, 21] | ['0x2', '0x15'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [2, 22] | ['0x2', '0x16'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [2, 26] | ['0x2', '0x1a'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [2, 27] | ['0x2', '0x1b'] | 0x2 | [0, 4] | ['0x0', '0x4'] | 4
0x2 | [2, 28] | ['0x2', '0x1c'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [2, 103] | ['0x2', '0x67'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [2, 109] | ['0x2', '0x6d'] | 0xff | [13] | 0xd | [13]
0x2 | [2, 111] | ['0x2', '0x6f'] | 0xff | [13] | 0xd | [13]
0x2 | [2, 112] | ['0x2', '0x70'] | 0xff | [13] | 0xd | [13]
0x2 | [2, 186] | ['0x2', '0xba'] | 0x2 | [31, 153] | ['0x1f', '0x99'] | 8089
0x2 | [2, 187] | ['0x2', '0xbb'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [2, 189] | ['0x2', '0xbd'] | 0x2 | [0, 2] | ['0x0', '0x2'] | 2
0x2 | [2, 192] | ['0x2', '0xc0'] | 0x2 | [0, 96] | ['0x0', '0x60'] | 96
0x2 | [2, 196] | ['0x2', '0xc4'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [2, 197] | ['0x2', '0xc5'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [2, 208] | ['0x2', '0xd0'] | 0x2 | [0, 5] | ['0x0', '0x5'] | 5
0x2 | [2, 209] | ['0x2', '0xd1'] | 0x2 | [0, 200] | ['0x0', '0xc8'] | 200
0x2 | [2, 210] | ['0x2', '0xd2'] | 0x2 | [0, 20] | ['0x0', '0x14'] | 20
0x2 | [3, 0] | ['0x3', '0x0'] | 0x2 | [30, 180] | ['0x1e', '0xb4'] | 7860
0x2 | [3, 22] | ['0x3', '0x16'] | 0x2 | [6, 64] | ['0x6', '0x40'] | 1600
0x2 | [3, 23] | ['0x3', '0x17'] | 0x2 | [0, 3] | ['0x0', '0x3'] | 3
0x2 | [3, 24] | ['0x3', '0x18'] | 0x2 | [0, 50] | ['0x0', '0x32'] | 50
0x2 | [3, 25] | ['0x3', '0x19'] | 0x2 | [0, 55] | ['0x0', '0x37'] | 55
0x2 | [3, 26] | ['0x3', '0x1a'] | 0x2 | [0, 30] | ['0x0', '0x1e'] | 30
0x2 | [3, 27] | ['0x3', '0x1b'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 33] | ['0x3', '0x21'] | 0x2 | [0, 8] | ['0x0', '0x8'] | 8
0x2 | [3, 34] | ['0x3', '0x22'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 35] | ['0x3', '0x23'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 36] | ['0x3', '0x24'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 37] | ['0x3', '0x25'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 38] | ['0x3', '0x26'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 39] | ['0x3', '0x27'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [3, 40] | ['0x3', '0x28'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
0x2 | [3, 41] | ['0x3', '0x29'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
0x2 | [3, 42] | ['0x3', '0x2a'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
0x2 | [3, 43] | ['0x3', '0x2b'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 44] | ['0x3', '0x2c'] | 0x2 | [0, 2] | ['0x0', '0x2'] | 2
0x2 | [3, 45] | ['0x3', '0x2d'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 46] | ['0x3', '0x2e'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 48] | ['0x3', '0x30'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 49] | ['0x3', '0x31'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 52] | ['0x3', '0x34'] | 0x2 | [13, 122] | ['0xd', '0x7a'] | 3450
0x2 | [3, 53] | ['0x3', '0x35'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [3, 54] | ['0x3', '0x36'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 55] | ['0x3', '0x37'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 56] | ['0x3', '0x38'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 57] | ['0x3', '0x39'] | 0x2 | [13, 122] | ['0xd', '0x7a'] | 3450
0x2 | [3, 58] | ['0x3', '0x3a'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [3, 59] | ['0x3', '0x3b'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 60] | ['0x3', '0x3c'] | 0x2 | [0, 2] | ['0x0', '0x2'] | 2
0x2 | [3, 61] | ['0x3', '0x3d'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 62] | ['0x3', '0x3e'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 133] | ['0x3', '0x85'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 134] | ['0x3', '0x86'] | 0x2 | [0, 2] | ['0x0', '0x2'] | 2
0x2 | [3, 135] | ['0x3', '0x87'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 136] | ['0x3', '0x88'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 137] | ['0x3', '0x89'] | 0x2 | [0, 3] | ['0x0', '0x3'] | 3
0x2 | [3, 138] | ['0x3', '0x8a'] | 0x2 | [0, 3] | ['0x0', '0x3'] | 3
0x2 | [3, 139] | ['0x3', '0x8b'] | 0x2 | [0, 3] | ['0x0', '0x3'] | 3
0x2 | [3, 140] | ['0x3', '0x8c'] | 0x2 | [0, 3] | ['0x0', '0x3'] | 3
0x2 | [3, 141] | ['0x3', '0x8d'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [3, 142] | ['0x3', '0x8e'] | 0x2 | [7, 208] | ['0x7', '0xd0'] | 2000
0x2 | [3, 143] | ['0x3', '0x8f'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
0x2 | [3, 144] | ['0x3', '0x90'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
0x2 | [3, 145] | ['0x3', '0x91'] | 0x2 | [8, 52] | ['0x8', '0x34'] | 2100
0x2 | [3, 146] | ['0x3', '0x92'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
0x2 | [3, 147] | ['0x3', '0x93'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
0x2 | [3, 148] | ['0x3', '0x94'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
0x2 | [3, 149] | ['0x3', '0x95'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 150] | ['0x3', '0x96'] | 0x2 | [1, 224] | ['0x1', '0xe0'] | 480
0x2 | [3, 151] | ['0x3', '0x97'] | 0x2 | [4, 213] | ['0x4', '0xd5'] | 1237
0x2 | [3, 152] | ['0x3', '0x98'] | 0x2 | [0, 40] | ['0x0', '0x28'] | 40
0x2 | [3, 153] | ['0x3', '0x99'] | 0x2 | [0, 50] | ['0x0', '0x32'] | 50
0x2 | [3, 154] | ['0x3', '0x9a'] | 0x2 | [0, 60] | ['0x0', '0x3c'] | 60
0x2 | [3, 155] | ['0x3', '0x9b'] | 0x2 | [0, 70] | ['0x0', '0x46'] | 70
0x2 | [3, 156] | ['0x3', '0x9c'] | 0x2 | [0, 80] | ['0x0', '0x50'] | 80
0x2 | [3, 157] | ['0x3', '0x9d'] | 0x2 | [0, 15] | ['0x0', '0xf'] | 15
0x2 | [3, 158] | ['0x3', '0x9e'] | 0x2 | [4, 56] | ['0x4', '0x38'] | 1080
0x2 | [3, 159] | ['0x3', '0x9f'] | 0x2 | [1, 79] | ['0x1', '0x4f'] | 335
0x2 | [3, 160] | ['0x3', '0xa0'] | 0x2 | [0, 45] | ['0x0', '0x2d'] | 45
0x2 | [3, 161] | ['0x3', '0xa1'] | 0x2 | [0, 55] | ['0x0', '0x37'] | 55
0x2 | [3, 162] | ['0x3', '0xa2'] | 0x2 | [0, 65] | ['0x0', '0x41'] | 65
0x2 | [3, 163] | ['0x3', '0xa3'] | 0x2 | [0, 75] | ['0x0', '0x4b'] | 75
0x2 | [3, 164] | ['0x3', '0xa4'] | 0x2 | [0, 85] | ['0x0', '0x55'] | 85
0x2 | [3, 165] | ['0x3', '0xa5'] | 0x2 | [2, 88] | ['0x2', '0x58'] | 600
0x2 | [3, 166] | ['0x3', '0xa6'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 167] | ['0x3', '0xa7'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 168] | ['0x3', '0xa8'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 169] | ['0x3', '0xa9'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 170] | ['0x3', '0xaa'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 171] | ['0x3', '0xab'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 172] | ['0x3', '0xac'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 173] | ['0x3', '0xad'] | 0x2 | [0, 180] | ['0x0', '0xb4'] | 180
0x2 | [3, 174] | ['0x3', '0xae'] | 0x2 | [13, 122] | ['0xd', '0x7a'] | 3450
0x2 | [3, 175] | ['0x3', '0xaf'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 176] | ['0x3', '0xb0'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 177] | ['0x3', '0xb1'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [3, 178] | ['0x3', '0xb2'] | 0x2 | [0, 40] | ['0x0', '0x28'] | 40
0x2 | [3, 179] | ['0x3', '0xb3'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 180] | ['0x3', '0xb4'] | 0x2 | [13, 122] | ['0xd', '0x7a'] | 3450
0x2 | [3, 181] | ['0x3', '0xb5'] | 0x2 | [0, 30] | ['0x0', '0x1e'] | 30
0x2 | [3, 182] | ['0x3', '0xb6'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [3, 183] | ['0x3', '0xb7'] | 0x2 | [13, 122] | ['0xd', '0x7a'] | 3450
0x2 | [3, 184] | ['0x3', '0xb8'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 185] | ['0x3', '0xb9'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10
0x2 | [3, 186] | ['0x3', '0xba'] | 0x2 | [4, 210] | ['0x4', '0xd2'] | 1234
0x2 | [3, 187] | ['0x3', '0xbb'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
0x2 | [3, 188] | ['0x3', '0xbc'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
0x2 | [3, 189] | ['0x3', '0xbd'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
0x2 | [3, 190] | ['0x3', '0xbe'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
0x2 | [3, 191] | ['0x3', '0xbf'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 192] | ['0x3', '0xc0'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 193] | ['0x3', '0xc1'] | 0x2 | [0, 1] | ['0x0', '0x1'] | 1
0x2 | [3, 194] | ['0x3', '0xc2'] | 0x2 | [5, 161] | ['0x5', '0xa1'] | 1441
0x2 | [3, 195] | ['0x3', '0xc3'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
0x2 | [3, 196] | ['0x3', '0xc4'] | 0x2 | [0, 10] | ['0x0', '0xa'] | 10

Getting and Setting Program RPM

To find our get addresses, we’ll set our four Programs to RPM values that don’t already appear anywhere in the data. For simplicity, let’s go with 1001, 1002, 1003, and 1004. After setting, we re-run the brute force and compare what changed:

--- output-1	2019-02-05 20:06:28.631870774 +0000
+++ output-2	2019-02-05 20:02:52.503650592 +0000
@@ -48 +48 @@
-0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [1, 239] | ['0x1', '0xef'] | 495
+0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [1, 234] | ['0x1', '0xea'] | 490
@@ -81,3 +81,3 @@
-0x2 | [3, 40] | ['0x3', '0x28'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
-0x2 | [3, 41] | ['0x3', '0x29'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
-0x2 | [3, 42] | ['0x3', '0x2a'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
+0x2 | [3, 40] | ['0x3', '0x28'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
+0x2 | [3, 41] | ['0x3', '0x29'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
+0x2 | [3, 42] | ['0x3', '0x2a'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
@@ -156,3 +156,3 @@
-0x2 | [3, 188] | ['0x3', '0xbc'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
-0x2 | [3, 189] | ['0x3', '0xbd'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
-0x2 | [3, 190] | ['0x3', '0xbe'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
+0x2 | [3, 188] | ['0x3', '0xbc'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
+0x2 | [3, 189] | ['0x3', '0xbd'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
+0x2 | [3, 190] | ['0x3', '0xbe'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100

Not exactly what we expected to see, but interesting none-the-less. According to the manual, the factory default Program RPMs are 750, 1500, 2350, and 3110. Several of these values were changed, but they all became 1100 (other than [2, 10] which I initially thought was a checksum or similar, but I’m now thinking is watts). My pump’s minimum speed is set to 1100 rpm, so I’m guessing that’s getting in the way. Let’s try using 1101, 1102, 1103, and 1104 instead and see where that gets us:

--- output-1	2019-02-05 20:06:28.631870774 +0000
+++ output-3	2019-02-05 20:51:13.036080393 +0000
@@ -46,3 +46,3 @@
-0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [217, 64] | ['0xd9', '0x40'] | 55616
-0x2 | [2, 6] | ['0x2', '0x6'] | 0x2 | [7, 208] | ['0x7', '0xd0'] | 2000
-0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [1, 239] | ['0x1', '0xef'] | 495
+0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [224, 192] | ['0xe0', '0xc0'] | 57536
+0x2 | [2, 6] | ['0x2', '0x6'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
+0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [0, 0] | ['0x0', '0x0'] | 0
@@ -62 +62 @@
-0x2 | [2, 196] | ['0x2', '0xc4'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
+0x2 | [2, 196] | ['0x2', '0xc4'] | 0x2 | [4, 77] | ['0x4', '0x4d'] | 1101
@@ -80,4 +80,4 @@
-0x2 | [3, 39] | ['0x3', '0x27'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
-0x2 | [3, 40] | ['0x3', '0x28'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
-0x2 | [3, 41] | ['0x3', '0x29'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
-0x2 | [3, 42] | ['0x3', '0x2a'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
+0x2 | [3, 39] | ['0x3', '0x27'] | 0x2 | [4, 77] | ['0x4', '0x4d'] | 1101
+0x2 | [3, 40] | ['0x3', '0x28'] | 0x2 | [4, 78] | ['0x4', '0x4e'] | 1102
+0x2 | [3, 41] | ['0x3', '0x29'] | 0x2 | [4, 79] | ['0x4', '0x4f'] | 1103
+0x2 | [3, 42] | ['0x3', '0x2a'] | 0x2 | [4, 80] | ['0x4', '0x50'] | 1104
@@ -155,4 +155,4 @@
-0x2 | [3, 187] | ['0x3', '0xbb'] | 0x2 | [4, 76] | ['0x4', '0x4c'] | 1100
-0x2 | [3, 188] | ['0x3', '0xbc'] | 0x2 | [5, 220] | ['0x5', '0xdc'] | 1500
-0x2 | [3, 189] | ['0x3', '0xbd'] | 0x2 | [9, 46] | ['0x9', '0x2e'] | 2350
-0x2 | [3, 190] | ['0x3', '0xbe'] | 0x2 | [12, 38] | ['0xc', '0x26'] | 3110
+0x2 | [3, 187] | ['0x3', '0xbb'] | 0x2 | [4, 77] | ['0x4', '0x4d'] | 1101
+0x2 | [3, 188] | ['0x3', '0xbc'] | 0x2 | [4, 78] | ['0x4', '0x4e'] | 1102
+0x2 | [3, 189] | ['0x3', '0xbd'] | 0x2 | [4, 79] | ['0x4', '0x4f'] | 1103
+0x2 | [3, 190] | ['0x3', '0xbe'] | 0x2 | [4, 80] | ['0x4', '0x50'] | 1104

Ok, we’ve got a few interesting things here. I don’t know what’s up with [1, 254], so we’ll keep watching it over time and see if it becomes more clear.

[2, 6] and [2, 10] look like current RPM and watts, respectively. We’ll try to confirm that later.

The next couple blocks look like what we’re expecting to see. Let’s compare these with some other leads we’ve found in the wild relating to ACTION == 0x01:

PROGRAM = {
    'OFF':              0x00,
    'RPM':              [0x02, 0xC4],
    'GPM':              [0x02, 0xE4],
    'EXTERNAL':         [0x03, 0x21],
    'SET_PROGRAM_1':    [0x03, 0x27],
    'SET_PROGRAM_2':    [0x03, 0x28],
    'SET_PROGRAM_3':    [0x03, 0x29],
    'SET_PROGRAM_4':    [0x03, 0x2a],
    'SET_TIMER':        [0x03, 0x2b],
}

Playing around with things a bit, I confirmed that I can set arbitrary program RPMs with ACTION = 0x01 and [DATA] addressing either ([0x03, 0x27] through [0x03, 0x2a]) or ([0x03, 0xbb] through [0x03, 0xbe]) along with 2-bytes representing the desired RPM and I can fetch these values using the same [DATA] but ACTION = 0x02. I have no idea why there are two address ranges that appear to do exactly the same thing. I confirmed that setting either one updates both.

Bringing it back home, I divided these up a bit for readability in the code:

PROGRAM = [         # Addresses for getting and setting Program RPMs
    None,
    [0x03, 0x27],   # Program 1
    [0x03, 0x28],   # Program 2
    [0x03, 0x29],   # Program 3
    [0x03, 0x2a],   # Program 4
    [0x03, 0xbb],   # Also Program 1?
    [0x03, 0xbc],   # Also Program 2?
    [0x03, 0xbd],   # Also Program 3?
    [0x03, 0xbe],   # Also Program 4?
]

MODE = {
    'OFF':              [0x00],
    'RPM':              [0x02, 0xC4],
    'GPM':              [0x02, 0xE4],
    'RUN_PROGRAM':      [0x03, 0x21],
    'SET_TIMER':        [0x03, 0x2b],
}

One interesting note is that while you can set RPM with a granularity of 1, when you run the Program, the running RPM has a granularity of 5, rounded down from the stored value. For example, if you ask for 1101 rpm, you’ll get 1100 and if you ask for 1107 you’re getting 1105. I’ve found this to be the case whether RPMs are requested directly, via preset Speed, or via preset Program.

Getting and Setting the Running Program

To find the currently running program, we’ll diff our brute data while running different Programs:

--- output-prog-1	2019-02-05 17:06:39.008057183 -0800
+++ output-prog-2	2019-02-05 17:08:43.296958204 -0800
@@ -46 +46 @@
-0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [207, 192] | ['0xcf', '0xc0'] | 53184
+0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [209, 64] | ['0xd1', '0x40'] | 53568
@@ -48 +48 @@
-0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [9, 86] | ['0x9', '0x56'] | 2390
+0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [9, 58] | ['0x9', '0x3a'] | 2362
@@ -74 +74 @@
-0x2 | [3, 33] | ['0x3', '0x21'] | 0x2 | [0, 8] | ['0x0', '0x8'] | 8
+0x2 | [3, 33] | ['0x3', '0x21'] | 0x2 | [0, 16] | ['0x0', '0x10'] | 16
--- output-prog-1	2019-02-05 17:06:39.008057183 -0800
+++ output-prog-3	2019-02-05 17:09:15.026676171 -0800
@@ -46,3 +46,3 @@
-0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [207, 192] | ['0xcf', '0xc0'] | 53184
-0x2 | [2, 6] | ['0x2', '0x6'] | 0x2 | [13, 122] | ['0xd', '0x7a'] | 3450
-0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [9, 86] | ['0x9', '0x56'] | 2390
+0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [203, 64] | ['0xcb', '0x40'] | 52032
+0x2 | [2, 6] | ['0x2', '0x6'] | 0x2 | [11, 34] | ['0xb', '0x22'] | 2850
+0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [5, 85] | ['0x5', '0x55'] | 1365
@@ -74 +74 @@
-0x2 | [3, 33] | ['0x3', '0x21'] | 0x2 | [0, 8] | ['0x0', '0x8'] | 8
+0x2 | [3, 33] | ['0x3', '0x21'] | 0x2 | [0, 24] | ['0x0', '0x18'] | 24
--- output-prog-1	2019-02-05 17:06:39.008057183 -0800
+++ output-prog-4	2019-02-05 17:09:42.866428277 -0800
@@ -46,3 +46,3 @@
-0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [207, 192] | ['0xcf', '0xc0'] | 53184
-0x2 | [2, 6] | ['0x2', '0x6'] | 0x2 | [13, 122] | ['0xd', '0x7a'] | 3450
-0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [9, 86] | ['0x9', '0x56'] | 2390
+0x2 | [1, 254] | ['0x1', '0xfe'] | 0x2 | [208, 0] | ['0xd0', '0x0'] | 53248
+0x2 | [2, 6] | ['0x2', '0x6'] | 0x2 | [10, 190] | ['0xa', '0xbe'] | 2750
+0x2 | [2, 10] | ['0x2', '0xa'] | 0x2 | [4, 237] | ['0x4', '0xed'] | 1261
@@ -74 +74 @@
-0x2 | [3, 33] | ['0x3', '0x21'] | 0x2 | [0, 8] | ['0x0', '0x8'] | 8
+0x2 | [3, 33] | ['0x3', '0x21'] | 0x2 | [0, 32] | ['0x0', '0x20'] | 32

Considering the different states of priming and such when the dumps were taken, I’d say this confirms that [2, 6] is current RPM, [2, 10] is current watts, and [3,33] is the currently running Program.