1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <errno.h>
18 #include <float.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <eventnums.h>
23 #include <gpio.h>
24 #include <heap.h>
25 #include <hostIntf.h>
26 #include <isr.h>
27 #include <i2c.h>
28 #include <nanohubPacket.h>
29 #include <sensors.h>
30 #include <seos.h>
31 #include <timer.h>
32 #include <util.h>
33
34 #include <cpu/cpuMath.h>
35
36 #include <plat/exti.h>
37 #include <plat/gpio.h>
38 #include <plat/syscfg.h>
39
40 #define S3708_APP_ID APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 13)
41 #define S3708_APP_VERSION 1
42
43 #define I2C_BUS_ID 0
44 #define I2C_SPEED 400000
45 #define I2C_ADDR 0x20
46
47 #define S3708_REG_PAGE_SELECT 0xFF
48
49 #define S3708_REG_F01_DATA_BASE 0x06
50 #define S3708_INT_STATUS_LPWG 0x04
51
52 #define S3708_REG_DATA_BASE 0x08
53 #define S3708_REG_DATA_4_OFFSET 0x02
54 #define S3708_INT_STATUS_DOUBLE_TAP 0x03
55
56 #define S3708_REG_F01_CTRL_BASE 0x14
57 #define S3708_NORMAL_MODE 0x00
58 #define S3708_SLEEP_MODE 0x01
59
60 #define S3708_REG_CTRL_BASE 0x1b
61 #define S3708_REG_CTRL_20_OFFSET 0x07
62 #define S3708_REPORT_MODE_CONT 0x00
63 #define S3708_REPORT_MODE_LPWG 0x02
64
65 #define MAX_PENDING_I2C_REQUESTS 4
66 #define MAX_I2C_TRANSFER_SIZE 8
67 #define MAX_I2C_RETRY_DELAY 250000000ull // 250 milliseconds
68 #define MAX_I2C_RETRY_COUNT (15000000000ull / MAX_I2C_RETRY_DELAY) // 15 seconds
69 #define HACK_RETRY_SKIP_COUNT 1
70
71 #define DEFAULT_PROX_RATE_HZ SENSOR_HZ(5.0f)
72 #define DEFAULT_PROX_LATENCY 0.0
73 #define PROXIMITY_THRESH_NEAR 5.0f // distance in cm
74
75 #define EVT_SENSOR_PROX sensorGetMyEventType(SENS_TYPE_PROX)
76
77 #define ENABLE_DEBUG 0
78
79 #define INFO_PRINT(fmt, ...) osLog(LOG_INFO, "[DoubleTouch] " fmt, ##__VA_ARGS__)
80 #define ERROR_PRINT(fmt, ...) osLog(LOG_ERROR, "[DoubleTouch] " fmt, ##__VA_ARGS__)
81 #if ENABLE_DEBUG
82 #define DEBUG_PRINT(fmt, ...) INFO_PRINT(fmt, ##__VA_ARGS__)
83 #else
84 #define DEBUG_PRINT(fmt, ...) ((void)0)
85 #endif
86
87
88 #ifndef TOUCH_PIN
89 #error "TOUCH_PIN is not defined; please define in variant.h"
90 #endif
91
92 #ifndef TOUCH_IRQ
93 #error "TOUCH_IRQ is not defined; please define in variant.h"
94 #endif
95
96 enum SensorEvents
97 {
98 EVT_SENSOR_I2C = EVT_APP_START + 1,
99 EVT_SENSOR_TOUCH_INTERRUPT,
100 EVT_SENSOR_RETRY_TIMER,
101 };
102
103 enum TaskState
104 {
105 STATE_ENABLE_0,
106 STATE_ENABLE_1,
107 STATE_ENABLE_2,
108 STATE_DISABLE_0,
109 STATE_INT_HANDLE_0,
110 STATE_INT_HANDLE_1,
111 STATE_IDLE,
112 STATE_CANCELLED,
113 };
114
115 struct I2cTransfer
116 {
117 size_t tx;
118 size_t rx;
119 int err;
120 uint8_t txrxBuf[MAX_I2C_TRANSFER_SIZE];
121 uint8_t state;
122 bool inUse;
123 };
124
125 struct TaskStatistics {
126 uint64_t enabledTimestamp;
127 uint64_t proxEnabledTimestamp;
128 uint64_t lastProxFarTimestamp;
129 uint64_t totalEnabledTime;
130 uint64_t totalProxEnabledTime;
131 uint64_t totalProxFarTime;
132 uint32_t totalProxBecomesFar;
133 uint32_t totalProxBecomesNear;
134 };
135
136 enum ProxState {
137 PROX_STATE_UNKNOWN,
138 PROX_STATE_NEAR,
139 PROX_STATE_FAR
140 };
141
142 static struct TaskStruct
143 {
144 struct Gpio *pin;
145 struct ChainedIsr isr;
146 struct TaskStatistics stats;
147 struct I2cTransfer transfers[MAX_PENDING_I2C_REQUESTS];
148 uint32_t id;
149 uint32_t handle;
150 uint32_t retryTimerHandle;
151 uint32_t retryCnt;
152 uint32_t proxHandle;
153 enum ProxState proxState;
154 bool on;
155 bool gestureEnabled;
156 bool isrEnabled;
157 } mTask;
158
enableInterrupt(bool enable)159 static inline void enableInterrupt(bool enable)
160 {
161 if (!mTask.isrEnabled && enable) {
162 extiEnableIntGpio(mTask.pin, EXTI_TRIGGER_FALLING);
163 extiChainIsr(TOUCH_IRQ, &mTask.isr);
164 } else if (mTask.isrEnabled && !enable) {
165 extiUnchainIsr(TOUCH_IRQ, &mTask.isr);
166 extiDisableIntGpio(mTask.pin);
167 }
168 mTask.isrEnabled = enable;
169 }
170
touchIsr(struct ChainedIsr * localIsr)171 static bool touchIsr(struct ChainedIsr *localIsr)
172 {
173 struct TaskStruct *data = container_of(localIsr, struct TaskStruct, isr);
174
175 if (!extiIsPendingGpio(data->pin)) {
176 return false;
177 }
178
179 osEnqueuePrivateEvt(EVT_SENSOR_TOUCH_INTERRUPT, NULL, NULL, data->id);
180
181 extiClearPendingGpio(data->pin);
182
183 return true;
184 }
185
i2cCallback(void * cookie,size_t tx,size_t rx,int err)186 static void i2cCallback(void *cookie, size_t tx, size_t rx, int err)
187 {
188 struct I2cTransfer *xfer = cookie;
189
190 xfer->tx = tx;
191 xfer->rx = rx;
192 xfer->err = err;
193
194 osEnqueuePrivateEvt(EVT_SENSOR_I2C, cookie, NULL, mTask.id);
195 // Do not print error for ENXIO since we expect there to be times where we
196 // cannot talk to the touch controller.
197 if (err == -ENXIO) {
198 DEBUG_PRINT("i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err);
199 } else if (err != 0) {
200 ERROR_PRINT("i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err);
201 }
202 }
203
retryTimerCallback(uint32_t timerId,void * cookie)204 static void retryTimerCallback(uint32_t timerId, void *cookie)
205 {
206 osEnqueuePrivateEvt(EVT_SENSOR_RETRY_TIMER, cookie, NULL, mTask.id);
207 }
208
209 // Allocate a buffer and mark it as in use with the given state, or return NULL
210 // if no buffers available. Must *not* be called from interrupt context.
allocXfer(uint8_t state)211 static struct I2cTransfer *allocXfer(uint8_t state)
212 {
213 size_t i;
214
215 for (i = 0; i < ARRAY_SIZE(mTask.transfers); i++) {
216 if (!mTask.transfers[i].inUse) {
217 mTask.transfers[i].inUse = true;
218 mTask.transfers[i].state = state;
219 memset(mTask.transfers[i].txrxBuf, 0x00, sizeof(mTask.transfers[i].txrxBuf));
220 return &mTask.transfers[i];
221 }
222 }
223
224 ERROR_PRINT("Ran out of I2C buffers!");
225 return NULL;
226 }
227
228 // Helper function to initiate the I2C transfer. Returns true is the transaction
229 // was successfully register by I2C driver. Otherwise, returns false.
performXfer(struct I2cTransfer * xfer,size_t txBytes,size_t rxBytes)230 static bool performXfer(struct I2cTransfer *xfer, size_t txBytes, size_t rxBytes)
231 {
232 int ret;
233
234 if ((txBytes > MAX_I2C_TRANSFER_SIZE) || (rxBytes > MAX_I2C_TRANSFER_SIZE)) {
235 ERROR_PRINT("txBytes and rxBytes must be less than %d", MAX_I2C_TRANSFER_SIZE);
236 return false;
237 }
238
239 if (rxBytes) {
240 ret = i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, txBytes, xfer->txrxBuf, rxBytes, i2cCallback, xfer);
241 } else {
242 ret = i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, txBytes, i2cCallback, xfer);
243 }
244
245 if (ret != 0) {
246 ERROR_PRINT("I2C transfer was not successful (error %d)!", ret);
247 }
248
249 return (ret == 0);
250 }
251
252 // Helper function to write a one byte register. Returns true if we got a
253 // successful return value from i2cMasterTx().
writeRegister(uint8_t reg,uint8_t value,uint8_t state)254 static bool writeRegister(uint8_t reg, uint8_t value, uint8_t state)
255 {
256 struct I2cTransfer *xfer = allocXfer(state);
257
258 if (xfer != NULL) {
259 xfer->txrxBuf[0] = reg;
260 xfer->txrxBuf[1] = value;
261 return performXfer(xfer, 2, 0);
262 }
263
264 return false;
265 }
266
setSleepEnable(bool enable,uint8_t state)267 static bool setSleepEnable(bool enable, uint8_t state)
268 {
269 return writeRegister(S3708_REG_F01_CTRL_BASE, enable ? S3708_SLEEP_MODE : S3708_NORMAL_MODE, state);
270 }
271
setReportingMode(uint8_t mode,uint8_t state)272 static bool setReportingMode(uint8_t mode, uint8_t state)
273 {
274 struct I2cTransfer *xfer;
275
276 xfer = allocXfer(state);
277 if (xfer != NULL) {
278 xfer->txrxBuf[0] = S3708_REG_CTRL_BASE + S3708_REG_CTRL_20_OFFSET;
279 xfer->txrxBuf[1] = 0x00;
280 xfer->txrxBuf[2] = 0x00;
281 xfer->txrxBuf[3] = mode;
282 return performXfer(xfer, 4, 0);
283 }
284
285 return false;
286 }
287
setRetryTimer()288 static void setRetryTimer()
289 {
290 mTask.retryCnt++;
291 if (mTask.retryCnt < MAX_I2C_RETRY_COUNT) {
292 mTask.retryTimerHandle = timTimerSet(MAX_I2C_RETRY_DELAY, 0, 50, retryTimerCallback, NULL, true);
293 if (!mTask.retryTimerHandle) {
294 ERROR_PRINT("failed to allocate timer");
295 }
296 } else {
297 ERROR_PRINT("could not communicate with touch controller");
298 }
299 }
300
setGesturePower(bool enable,bool skipI2c)301 static void setGesturePower(bool enable, bool skipI2c)
302 {
303 bool ret;
304 size_t i;
305
306 INFO_PRINT("gesture: %d", enable);
307
308 // Cancel any pending I2C transactions by changing the callback state
309 for (i = 0; i < ARRAY_SIZE(mTask.transfers); i++) {
310 if (mTask.transfers[i].inUse) {
311 mTask.transfers[i].state = STATE_CANCELLED;
312 }
313 }
314
315 if (enable) {
316 mTask.retryCnt = 0;
317
318 // Set page number to 0x00
319 ret = writeRegister(S3708_REG_PAGE_SELECT, 0x00, STATE_ENABLE_0);
320 } else {
321 // Cancel any pending retries
322 if (mTask.retryTimerHandle) {
323 timTimerCancel(mTask.retryTimerHandle);
324 mTask.retryTimerHandle = 0;
325 }
326
327 if (skipI2c) {
328 ret = true;
329 } else {
330 // Reset to continuous reporting mode
331 ret = setReportingMode(S3708_REPORT_MODE_CONT, STATE_DISABLE_0);
332 }
333 }
334
335 if (ret) {
336 mTask.gestureEnabled = enable;
337 enableInterrupt(enable);
338 }
339 }
340
configProx(bool on)341 static void configProx(bool on) {
342 if (on) {
343 mTask.stats.proxEnabledTimestamp = sensorGetTime();
344 sensorRequest(mTask.id, mTask.proxHandle, DEFAULT_PROX_RATE_HZ,
345 DEFAULT_PROX_LATENCY);
346 osEventSubscribe(mTask.id, EVT_SENSOR_PROX);
347 } else {
348 sensorRelease(mTask.id, mTask.proxHandle);
349 osEventUnsubscribe(mTask.id, EVT_SENSOR_PROX);
350
351 mTask.stats.totalProxEnabledTime += sensorGetTime() - mTask.stats.proxEnabledTimestamp;
352 if (mTask.proxState == PROX_STATE_FAR) {
353 mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp;
354 }
355 }
356 mTask.proxState = PROX_STATE_UNKNOWN;
357 }
358
callbackPower(bool on,void * cookie)359 static bool callbackPower(bool on, void *cookie)
360 {
361 uint32_t enabledSeconds, proxEnabledSeconds, proxFarSeconds;
362
363 INFO_PRINT("power: %d", on);
364
365 if (on) {
366 mTask.stats.enabledTimestamp = sensorGetTime();
367 } else {
368 mTask.stats.totalEnabledTime += sensorGetTime() - mTask.stats.enabledTimestamp;
369 }
370
371 enabledSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalEnabledTime, 1000000000);
372 proxEnabledSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalProxEnabledTime, 1000000000);
373 proxFarSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalProxFarTime, 1000000000);
374 INFO_PRINT("STATS: enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
375 ", prox enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
376 ", prox far %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
377 ", prox *->f %" PRIu32
378 ", prox *->n %" PRIu32,
379 enabledSeconds / 3600, (enabledSeconds % 3600) / 60, enabledSeconds % 60,
380 proxEnabledSeconds / 3600, (proxEnabledSeconds % 3600) / 60, proxEnabledSeconds % 60,
381 proxFarSeconds / 3600, (proxFarSeconds % 3600) / 60, proxFarSeconds % 60,
382 mTask.stats.totalProxBecomesFar,
383 mTask.stats.totalProxBecomesNear);
384
385 // If the task is disabled, that means the AP is on and has switched the I2C
386 // mux. Therefore, no I2C transactions will succeed so skip them.
387 if (mTask.gestureEnabled) {
388 setGesturePower(false, true /* skipI2c */);
389 }
390
391 mTask.on = on;
392 configProx(on);
393
394 return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, mTask.on, 0);
395 }
396
callbackFirmwareUpload(void * cookie)397 static bool callbackFirmwareUpload(void *cookie)
398 {
399 return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
400 }
401
callbackSetRate(uint32_t rate,uint64_t latency,void * cookie)402 static bool callbackSetRate(uint32_t rate, uint64_t latency, void *cookie)
403 {
404 return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
405 }
406
callbackFlush(void * cookie)407 static bool callbackFlush(void *cookie)
408 {
409 return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_DOUBLE_TOUCH), SENSOR_DATA_EVENT_FLUSH, NULL);
410 }
411
412 static const struct SensorInfo mSensorInfo = {
413 .sensorName = "Double Touch",
414 .sensorType = SENS_TYPE_DOUBLE_TOUCH,
415 .numAxis = NUM_AXIS_EMBEDDED,
416 .interrupt = NANOHUB_INT_WAKEUP,
417 .minSamples = 20
418 };
419
420 static const struct SensorOps mSensorOps =
421 {
422 .sensorPower = callbackPower,
423 .sensorFirmwareUpload = callbackFirmwareUpload,
424 .sensorSetRate = callbackSetRate,
425 .sensorFlush = callbackFlush,
426 };
427
processI2cResponse(struct I2cTransfer * xfer)428 static void processI2cResponse(struct I2cTransfer *xfer)
429 {
430 struct I2cTransfer *nextXfer;
431 union EmbeddedDataPoint sample;
432
433 switch (xfer->state) {
434 case STATE_ENABLE_0:
435 setSleepEnable(false, STATE_ENABLE_1);
436 break;
437
438 case STATE_ENABLE_1:
439 // HACK: DozeService reactivates pickup gesture before the screen
440 // comes on, so we need to wait for some time after enabling before
441 // trying to talk to touch controller. We may see the touch
442 // controller on the first few samples and then have communication
443 // switched off. So, wait HACK_RETRY_SKIP_COUNT samples before we
444 // consider the transaction.
445 if (mTask.retryCnt < HACK_RETRY_SKIP_COUNT) {
446 setRetryTimer();
447 } else {
448 setReportingMode(S3708_REPORT_MODE_LPWG, STATE_ENABLE_2);
449 }
450 break;
451
452 case STATE_ENABLE_2:
453 // Poll the GPIO line to see if it is low/active (it might have been
454 // low when we enabled the ISR, e.g. due to a pending touch event).
455 // Only do this after arming the LPWG, so it happens after we know
456 // that we can talk to the touch controller.
457 if (!gpioGet(mTask.pin)) {
458 osEnqueuePrivateEvt(EVT_SENSOR_TOUCH_INTERRUPT, NULL, NULL, mTask.id);
459 }
460 break;
461
462 case STATE_DISABLE_0:
463 setSleepEnable(true, STATE_IDLE);
464 break;
465
466 case STATE_INT_HANDLE_0:
467 // If the interrupt was from the LPWG function, read the function interrupt status register
468 if (xfer->txrxBuf[1] & S3708_INT_STATUS_LPWG) {
469 nextXfer = allocXfer(STATE_INT_HANDLE_1);
470 if (nextXfer != NULL) {
471 nextXfer->txrxBuf[0] = S3708_REG_DATA_BASE + S3708_REG_DATA_4_OFFSET;
472 performXfer(nextXfer, 1, 5);
473 }
474 }
475 break;
476
477 case STATE_INT_HANDLE_1:
478 // Verify the LPWG interrupt status
479 if (xfer->txrxBuf[0] & S3708_INT_STATUS_DOUBLE_TAP) {
480 DEBUG_PRINT("Sending event");
481 sample.idata = 1;
482 osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_DOUBLE_TOUCH), sample.vptr, NULL);
483 }
484 break;
485
486 default:
487 break;
488 }
489 }
490
handleI2cEvent(struct I2cTransfer * xfer)491 static void handleI2cEvent(struct I2cTransfer *xfer)
492 {
493 if (xfer->err == 0) {
494 processI2cResponse(xfer);
495 } else if (xfer->state == STATE_ENABLE_0 || xfer->state == STATE_ENABLE_1) {
496 setRetryTimer();
497 }
498
499 xfer->inUse = false;
500 }
501
handleEvent(uint32_t evtType,const void * evtData)502 static void handleEvent(uint32_t evtType, const void* evtData)
503 {
504 struct I2cTransfer *xfer;
505 union EmbeddedDataPoint embeddedSample;
506 enum ProxState lastProxState;
507 int ret;
508
509 switch (evtType) {
510 case EVT_APP_START:
511 osEventUnsubscribe(mTask.id, EVT_APP_START);
512 ret = i2cMasterRequest(I2C_BUS_ID, I2C_SPEED);
513 // Since the i2c bus can be shared with other drivers, it is
514 // possible that one of the other drivers requested the bus first.
515 // Therefore, either 0 or -EBUSY is an acceptable return.
516 if ((ret < 0) && (ret != -EBUSY)) {
517 ERROR_PRINT("i2cMasterRequest() failed!");
518 }
519
520 sensorFind(SENS_TYPE_PROX, 0, &mTask.proxHandle);
521
522 sensorRegisterInitComplete(mTask.handle);
523 break;
524
525 case EVT_SENSOR_I2C:
526 handleI2cEvent((struct I2cTransfer *)evtData);
527 break;
528
529 case EVT_SENSOR_TOUCH_INTERRUPT:
530 if (mTask.on) {
531 // Read the interrupt status register
532 xfer = allocXfer(STATE_INT_HANDLE_0);
533 if (xfer != NULL) {
534 xfer->txrxBuf[0] = S3708_REG_F01_DATA_BASE;
535 performXfer(xfer, 1, 2);
536 }
537 }
538 break;
539
540 case EVT_SENSOR_PROX:
541 if (mTask.on) {
542 // cast off the const, and cast to union
543 embeddedSample = (union EmbeddedDataPoint)((void*)evtData);
544 lastProxState = mTask.proxState;
545 mTask.proxState = (embeddedSample.fdata < PROXIMITY_THRESH_NEAR) ? PROX_STATE_NEAR : PROX_STATE_FAR;
546
547 if ((lastProxState != PROX_STATE_FAR) && (mTask.proxState == PROX_STATE_FAR)) {
548 ++mTask.stats.totalProxBecomesFar;
549 mTask.stats.lastProxFarTimestamp = sensorGetTime();
550 setGesturePower(true, false);
551 } else if ((lastProxState != PROX_STATE_NEAR) && (mTask.proxState == PROX_STATE_NEAR)) {
552 ++mTask.stats.totalProxBecomesNear;
553 if (lastProxState == PROX_STATE_FAR) {
554 mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp;
555 setGesturePower(false, false);
556 }
557 }
558 }
559 break;
560
561 case EVT_SENSOR_RETRY_TIMER:
562 if (mTask.on) {
563 // Set page number to 0x00
564 writeRegister(S3708_REG_PAGE_SELECT, 0x00, STATE_ENABLE_0);
565 }
566 break;
567 }
568 }
569
startTask(uint32_t taskId)570 static bool startTask(uint32_t taskId)
571 {
572 mTask.id = taskId;
573 mTask.handle = sensorRegister(&mSensorInfo, &mSensorOps, NULL, false);
574
575 mTask.pin = gpioRequest(TOUCH_PIN);
576 gpioConfigInput(mTask.pin, GPIO_SPEED_LOW, GPIO_PULL_NONE);
577 syscfgSetExtiPort(mTask.pin);
578 mTask.isr.func = touchIsr;
579
580 mTask.stats.totalProxBecomesFar = 0;
581 mTask.stats.totalProxBecomesNear = 0;
582
583 osEventSubscribe(taskId, EVT_APP_START);
584 return true;
585 }
586
endTask(void)587 static void endTask(void)
588 {
589 enableInterrupt(false);
590 extiUnchainIsr(TOUCH_IRQ, &mTask.isr);
591 extiClearPendingGpio(mTask.pin);
592 gpioRelease(mTask.pin);
593
594 i2cMasterRelease(I2C_BUS_ID);
595
596 sensorUnregister(mTask.handle);
597 }
598
599 INTERNAL_APP_INIT(S3708_APP_ID, S3708_APP_VERSION, startTask, endTask, handleEvent);
600