/
KatWalkC2.kt
317 lines (269 loc) · 10.3 KB
/
KatWalkC2.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
package com.datacompboy.nativekatgateway.driver
import android.util.Log
import com.google.ar.sceneform.math.Quaternion
import java.util.LinkedList
import kotlin.math.abs
import kotlin.math.atan2
enum class SENSOR {
NONE,
DIRECTION,
LEFT_FOOT,
RIGHT_FOOT,
}
class KatWalkC2 {
open class Sensor {
internal var _charging: Boolean = false
val charging: Boolean
get() = _charging
internal var _version: Int = -1
val version: Int
get() = _version
internal var _sensorId: Int = -1
val sensorId: Int
get() = _sensorId
internal var _charge: Float = 0f
val charge: Float
get() = _charge
open fun parsePacket(packet: ByteArray) {}
}
class DirectionSensor : Sensor() {
protected var _direction: Quaternion = Quaternion.identity()
protected var _angleDeg: Float = 0f
protected var _angleZero: Float = 0f
var direction: Quaternion
get() = _direction
set(value) {
_direction = value
val _angle = atan2(2 * (value.w * value.y - value.x * value.z), (value.w*value.w + value.x*value.x - value.y*value.y - value.z*value.z))
_angleDeg = (_angle * 180.0f / Math.PI).toFloat()
}
val angleDeg: Float
get() = normalAngle(_angleDeg - _angleZero)
fun normalAngle(x: Float): Float {
if (x > 360f) {
return x - 360f
}
if (x < 0f) {
return x + 360f
}
return x
}
override fun parsePacket(packet: ByteArray) {
val m15 = 0.000030517578125f // 2^-15 for quaternion conversion
val q1 = readShort(packet, 7)
val q2 = readShort(packet, 9)
val q3 = readShort(packet, 11)
val q4 = readShort(packet, 13)
direction = Quaternion(
(+ q1 - q2 - q3 + q4) * m15,
(- q1 - q2 + q3 + q4) * m15,
(+ q1 + q2 + q3 + q4) * m15,
(+ q1 - q2 + q3 - q4) * m15
).normalized()
if (packet[25].toInt() < 0) {
_angleZero = _angleDeg
}
/*
float fix8 = 0.00390625F; // 2^-8 for three more vars, always zero
float v1 = readShort(packet, 15) * fix8;
float v2 = readShort(packet, 17) * fix8;
float v3 = readShort(packet, 19) * fix8;
*/
}
}
class FootSensor : Sensor() {
protected var _move_x: Float = 0f
val move_x: Float
get() = _move_x
protected var _move_y: Float = 0f
val move_y: Float
get() = _move_y
protected var _shade: Float = 0f
val shade: Float
get() = _shade
protected var _ground: Boolean = false
val ground: Boolean
get() = _ground
override fun parsePacket(packet: ByteArray) {
_move_x = readShort(packet, 21) / 59055.117f
_move_y = readShort(packet, 23) / 59055.117f
// something = readShort(bytes, 9);
_shade = packet[26].toInt() / 127f
_ground = (packet[9] == 0.toByte())
}
}
val Direction = DirectionSensor()
val LeftFoot = FootSensor()
val RightFoot = FootSensor()
// Public interface
fun SetLEDLevel(level: Float) {
_sendQueue.addLast(KatLEDPacket(level))
}
fun StopStream() {
_sendQueue.addLast(KatStopStreamPacket())
}
fun GetStopStream(): ByteArray {
return KatStopStreamPacket().packet
}
fun StartStream() {
_sendQueue.addLast(KatStartStreamPacket())
}
fun GetStartStream(): ByteArray {
return KatStartStreamPacket().packet
}
fun SendRawData(data: ByteArray) {
_sendQueue.addLast(KatRawDataPacket(data))
}
// Low level protocol
private var _bad_packets = 0
private val _sendQueue = LinkedList<KatPacket>()
@OptIn(ExperimentalStdlibApi::class)
fun HandlePacket(packet: ByteArray, isControl: Boolean = false): Pair<SENSOR, ByteArray?> {
var updated: SENSOR = SENSOR.NONE
if (isKatPacket(packet)) {
// Log.i("HandlePacket", packet.toHexString(HexFormat.UpperCase) + " / " + isControl)
_bad_packets = 0
when (packet[5].toUInt()) {
0x30u -> { // Streaming update
when (packet[6].toInt()) {
Direction._sensorId -> { // direction sensor update
Direction.parsePacket(packet)
updated = SENSOR.DIRECTION
}
LeftFoot._sensorId -> { // left foot update
LeftFoot.parsePacket(packet)
updated = SENSOR.LEFT_FOOT
}
RightFoot._sensorId -> { // right foot update
RightFoot.parsePacket(packet)
updated = SENSOR.RIGHT_FOOT
}
else -> {
// not yet got configuration update i think
}
}
}
0x31u -> { // Stop Read confirmation.
// Stop correctly handled, time to re-start.
// TODO: report ALL sensor gone
return Pair(SENSOR.NONE, KatStartStreamPacket().packet)
}
0x32u -> { // Sensor configuration
var sensor: Sensor? = null
when (packet[7].toInt()) {
1 -> {
updated = SENSOR.DIRECTION
sensor = Direction
}
2 -> {
updated = SENSOR.LEFT_FOOT
sensor = LeftFoot
}
3 -> {
updated = SENSOR.RIGHT_FOOT
sensor = RightFoot
}
// else -> sensor = null
}
sensor?.let {
it._version = packet[11].toInt()
it._charge = readShort(packet, 9).toFloat()
it._charging = (packet[8].toInt()) > 0
it._sensorId = packet[6].toInt()
}
}
0x33u -> {
// TODO()
// Contains charge level at least, but KatC2 Driver ignores packet...
}
else -> {
// TODO()
// Support other messages eventually
}
}
if (_sendQueue.isNotEmpty()) {
return Pair(updated, _sendQueue.removeFirst().packet)
}
} else {
_bad_packets++
Log.i("HandlePacket", packet.toHexString(HexFormat.UpperCase) + " / " + isControl)
// Try packet recovery:
// If we got formed packet but mis-aligned signature, then send reset immediately
if (packet.size == 32 && packet[0].toUByte() == 0x1Fu.toUByte()) {
var p = packet[31].toUByte()
for (i in 1..31) {
val n = packet[i].toUByte()
if (p == 0x55u.toUByte() && n == 0xAAu.toUByte()) {
return Pair(updated, KatStopStreamPacket().packet) // KatStartStreamPacket().packet)
}
p = n
}
}
if (_bad_packets % 2 == 1 && _sendQueue.isNotEmpty()) {
Log.i("HandlePacket", "Send cmd in meanwhile")
return Pair(updated, _sendQueue.removeFirst().packet)
}
if (_bad_packets > 5) {
_bad_packets = 0
Log.i("HandlePacket", "Send start stream")
return Pair(updated, KatStartStreamPacket().packet)
}
}
return Pair(updated, null)
}
open class KatPacket {
internal val _packet = ByteArray(32)
val packet: ByteArray
get() = _packet
constructor() {
_packet[0] = 0x1F
_packet[1] = 0x55
_packet[2] = 0xAA.toByte()
_packet[3] = 0x00
_packet[4] = 0x00
}
}
class KatLEDPacket : KatPacket {
constructor(level: Float) : super() {
_packet[5] = 0xA1.toByte() // Set param
_packet[6] = 0x01 // Led
_packet[7] = 0x02 // Args size
writeShortMSB(_packet, 8, (level * 1000f).toInt())
}
}
class KatStartStreamPacket : KatPacket {
constructor() : super() {
_packet[5] = 0x30
}
}
class KatStopStreamPacket : KatPacket {
constructor() : super() {
_packet[5] = 0x31
}
}
class KatRawDataPacket : KatPacket {
constructor(rawpacket: ByteArray) {
rawpacket.copyInto(_packet, 0, 0, rawpacket.size)
}
}
}
private fun readShort(bytes: ByteArray, offset: Int): Int {
return (bytes[offset].toUByte().toInt() + (bytes[offset + 1].toInt() shl 8))
}
// Write "norma" short: least significant byte first
private fun writeShort(bytes: ByteArray, offset: Int, value: Int) {
bytes[offset + 0] = (value and 0xFF).toByte()
bytes[offset + 1] = ((value shr 8) and 0xFF).toByte()
}
// Write reversed order: most significant byte first
private fun writeShortMSB(bytes: ByteArray, offset: Int, value: Int) {
bytes[offset + 0] = ((value shr 8) and 0xFF).toByte()
bytes[offset + 1] = (value and 0xFF).toByte()
}
private fun isKatPacket(bytes: ByteArray): Boolean {
return (bytes[0].toUByte() == 0x1Fu.toUByte()) &&
(bytes[1].toUByte() == 0x55u.toUByte()) &&
(bytes[2].toUByte() == 0xAAu.toUByte()) &&
(bytes[3].toUByte() == 0x00u.toUByte()) &&
(bytes[4].toUByte() == 0x00u.toUByte())
}