diff --git a/.gitmodules b/.gitmodules index 452224ad72ce457e8e43e55f32b54ed216837e9b..6dd7bd5323d60f7d8437d2d75bc16fef600a57e9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "libs/eigen"] path = libs/eigen url = https://gitlab.com/libeigen/eigen.git +[submodule "libs/mxgui"] + path = libs/mxgui + url = git@git.skywarder.eu:scs/mxgui.git diff --git a/Makefile.template b/Makefile.template index ca62562a9f898923e20fb8ecbe7c4305679bb803..3b3ea18e0452bc4bc7eda4146e7deabbad50703b 100644 --- a/Makefile.template +++ b/Makefile.template @@ -15,7 +15,7 @@ include $(CONFPATH)/config/Makefile.inc ## ## List here subdirectories which contains makefiles ## -SUBDIRS := $(KPATH) {SBS_PROJECT_SUBDIRS} +SUBDIRS := $(KPATH) {SBS_PROJECT_SUBDIRS} {SBS_LIB_SUBDIR} ## ## List here your source files (both .s, .c and .cpp) @@ -25,13 +25,13 @@ SRC := {SBS_SOURCE_FILES} ## ## List here additional static libraries with relative path ## -LIBS := {SBS_PROJECT_LIBS} +LIBS := {SBS_PROJECT_LIBS} {SBS_LIB_LIBS} ## ## List here additional include directories (in the form -Iinclude_dir) ## INCLUDE_DIRS := -I$(SBS_BASE)/libs -I$(SBS_BASE)/libs/miosix-kernel/miosix \ - {SBS_PROJECT_INCLUDES} + {SBS_PROJECT_INCLUDES} {SBS_LIB_INCLUDES} ############################################################################## ## You should not need to modify anything below ## @@ -54,10 +54,12 @@ OBJ := $(addprefix $(OBJDIR)/, $(addsuffix .o, $(basename $(SRC)))) ## Always include CONFPATH first, as it overrides the config file location CXXFLAGS := $(CXXFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC) \ -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC) \ - -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -pedantic + -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -Wcast-align \ + -pedantic CFLAGS := $(CFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC) \ -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC) \ - -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -pedantic + -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -Wcast-align \ + -pedantic AFLAGS := $(AFLAGS_BASE) LFLAGS := $(LFLAGS_BASE) -Wl,--gc-sections,-Map,$(BINDIR)/${SBS_BIN_NAME}.map DFLAGS := -MMD -MP diff --git a/libs/miosix-kernel b/libs/miosix-kernel index 7cda1e7c1d55766f2f75806b56e4bad60db1fcf7..7ff90e110b1e0cb0c7acc7c652cf593087ccd18a 160000 --- a/libs/miosix-kernel +++ b/libs/miosix-kernel @@ -1 +1 @@ -Subproject commit 7cda1e7c1d55766f2f75806b56e4bad60db1fcf7 +Subproject commit 7ff90e110b1e0cb0c7acc7c652cf593087ccd18a diff --git a/libs/mxgui b/libs/mxgui new file mode 160000 index 0000000000000000000000000000000000000000..cab17f6185da95502d20dd31b17fef67e06ca6ef --- /dev/null +++ b/libs/mxgui @@ -0,0 +1 @@ +Subproject commit cab17f6185da95502d20dd31b17fef67e06ca6ef diff --git a/sbs b/sbs index d159ba3a51bb38c2280f2f0853a1aa75da16d187..6c5201fe2bd2d0f3c0f3ea6699ff72b552a857c0 100755 --- a/sbs +++ b/sbs @@ -48,9 +48,8 @@ except ImportError: # Global variables # srcfiles={} +extlibs={} mainfiles={} -tests={} -entrypoints={} colors=False # Map for the project configuration, filled in when parsing sbs.conf @@ -154,27 +153,30 @@ def parseConf(path): projconf[i] = conf.get('SBS_PROJECT', i) elif stype == 'srcfiles': srcfiles[i] = [x.strip() for x in conf.get(i, 'Files').split("\n")] - elif stype == 'board': - entrypoints[i] = { 'id': conf.get(i, 'BoardId'), - 'bin': conf.get(i, 'BinName'), - 'defines': conf.get(i, 'Defines'), - 'files': [entrypoint_mask % conf.get(i, 'Main').strip()] + - [x.strip() for x in conf.get(i, 'Include').split(' ')] - } - elif stype == 'test': - tests[i] = { 'id': conf.get(i, 'BoardId'), + elif stype == 'library': + extlibs[i] = {'subdir': conf.get(i, 'Subdir'), + 'libs': [x.strip() for x in conf.get(i, 'Libs').split()], + 'includes': [x.strip() for x in conf.get(i, 'Includes').split()]} + elif stype == 'board' or stype == 'test': + if stype == 'test': + mask = test_mask + add_def = " -I" + projconf['TESTS_PATH'] + else: + mask = entrypoint_mask + add_def = "" + + mainfiles[i] = {'type': stype, + 'id': conf.get(i, 'BoardId').strip(), 'bin': conf.get(i, 'BinName'), - 'defines': conf.get(i, 'Defines') + " -I" + projconf['TESTS_PATH'], - 'files': [test_mask % conf.get(i, 'Main').strip()] + + 'defines': conf.get(i, 'Defines') + add_def, + 'libs': [x.strip() for x in conf.get(i, 'Libs', fallback="").split()], + 'files': [mask % conf.get(i, 'Main').strip()] + [x.strip() for x in conf.get(i, 'Include').split(' ')] } else: print('[SBS] Syntax error in %s: type %s not implemented' % (path, stype)) exit(1) - # Join tests and entrypoints in mainfiles - mainfiles.update(entrypoints) - mainfiles.update(tests) # Exit if nothing was found if (len(mainfiles) == 0 ): @@ -184,6 +186,7 @@ def parseConf(path): # Import also srcfiles from boardcore if executing from another folders if projconf['SBS_BASE'] != '.': importSrcFiles(projconf['SBS_BASE'] + "/sbs.conf") + importLibs(projconf['SBS_BASE'] + "/sbs.conf") # Substitute includes for i in mainfiles: @@ -194,6 +197,13 @@ def parseConf(path): else: files += [j] mainfiles[i]['files'] = files + + # Substitute libs + for i in mainfiles: + lib_dicts = {} + for j in mainfiles[i]['libs']: + lib_dicts[j] = extlibs[j] + mainfiles[i]['libs'] = lib_dicts # # Create a Makefile for a specific board starting from Makefile.template @@ -208,7 +218,10 @@ def build_makefile(template, board, bname): "MAP_FILE": "export MAIN_MAP_FILE := bin/%s.map\n" % board['bin'], "SOURCE_FILES": "%s\n" % (" ".join(board['files'])), "CUSTOM_DEFINES": board['defines'], - "BIN_NAME": board['bin'] + "BIN_NAME": board['bin'], + "LIB_SUBDIR": " ".join([x['subdir'] for _,x in board['libs'].items()]), + "LIB_LIBS": " ".join([" ".join(x['libs']) for _,x in board['libs'].items()]), + "LIB_INCLUDES": " ".join([" ".join([ "-I" + s for s in x['includes']]) for _,x in board['libs'].items()]) } rmap["PROJECT_INCLUDES"] = "-I" + projconf["SRC_PATH"].strip() + " " @@ -255,7 +268,24 @@ def importSrcFiles(path): else: print('[SBS] Syntax error in %s: a srcfiles group named %s is already defined. Terminating' % (path, i)) exit(1) +# +# Import libraries groups from another .conf file. +# Libraries are added only if there's not a group already with the same name. +# +def importLibs(path): + conf = cp.RawConfigParser() + conf.read(path) + for i in conf.sections(): + stype = conf.get(i, 'Type') + if stype == 'library': + if i not in extlibs: + extlibs[i] = {'subdir': conf.get(i, 'Subdir'), + 'libs': [x.strip() for x in conf.get(i, 'Libs').split()], + 'includes': [x.strip() for x in conf.get(i, 'Includes').split()]} + else: + print('[SBS] Syntax error in %s: a library group named %s is already defined. Terminating' % (path, i)) + exit(1) ##------------------------------------------------------------- ## MAIN @@ -297,14 +327,17 @@ if options.lint == True: # List if options.list == True: print('[SBS] List of available entrypoints:') - for entrypoint in entrypoints: - print(entrypoint) + for main in mainfiles: + if(mainfiles[main]['type'] == 'board'): + print(main) print('\n[SBS] List of available tests:') - for test in tests: - print(test) + for main in mainfiles: + if(mainfiles[main]['type'] == 'test'): + print(main) exit(0) # Set how many main files to build: +files = list(mainfiles.keys()).copy() if options.board != None: # just one try: good = mainfiles[options.board] @@ -313,9 +346,13 @@ if options.board != None: # just one print('[SBS] No entrypoint or test named %s. Terminating.' % (options.board)) sys.exit(2) elif options.all_entry == True: # all entrypoints - mainfiles = entrypoints + for main in files: + if(mainfiles[main]['type'] == 'test'): + del mainfiles[main] elif options.all_tests == True: # all tests - mainfiles = tests + for main in files: + if(mainfiles[main]['type'] == 'board'): + del mainfiles[main] # Set action diff --git a/sbs.conf b/sbs.conf index e05829a53834135e832ef40df48ca669bdf09d39..a02fab8cf725ef8abc40b080ebb333e3ff4e2e89 100644 --- a/sbs.conf +++ b/sbs.conf @@ -77,16 +77,36 @@ ENTRY_PATH: src/entrypoints TESTS_PATH: src/tests SRC_PATH: src/shared SBS_BASE: . -PROJECT_INCLUDES: -Ilibs/eigen/ -PROJECT_SUBDIRS: -PROJECT_LIBS: +PROJECT_INCLUDES: +PROJECT_SUBDIRS: +PROJECT_LIBS: + +#----------------------------------------------# +# List of External Libraries # +#----------------------------------------------# +# [name] -> can be included using 'name' +# Type: library +# Subdir: Subdirectory containing the library makefile +# Libs: Static libraries to link against (whitespace-separated) +# Includes: List of additional include directories (whitespace-separated) +[libmxgui] +Type: library +Subdir: libs/mxgui +Libs: libs/mxgui/libmxgui.a +Includes: libs/mxgui + +[eigen] +Type: library +Subdir: +Libs: +Includes: libs/eigen #-----------------------------------# # List of Sources # #-----------------------------------# # [name] -> can be included using '%name' # Type: srcfiles -# Files: a '/n'-separated list of f +# Files: a '/n'-separated list of source files [shared] Type: srcfiles @@ -121,8 +141,7 @@ Files: src/shared/drivers/pwm/pwm.cpp [spi] Type: srcfiles -Files: src/shared/drivers/spi/SPIBus.cpp - src/shared/drivers/spi/SPITransaction.cpp +Files: src/shared/drivers/spi/SPITransaction.cpp [i2c] Type: srcfiles @@ -163,7 +182,9 @@ Files: src/shared/drivers/gamma868/Gamma868.cpp [xbee] Type: srcfiles -Files: src/shared/drivers/Xbee/Xbee.cpp +Files: src/shared/drivers/Xbee/APIFrameParser.cpp + src/shared/drivers/Xbee/Xbee.cpp + [events] Type: srcfiles @@ -192,6 +213,8 @@ Files: src/tests/catch/test-aero.cpp src/tests/catch/test-matrix.cpp src/tests/catch/test-packetqueue.cpp src/tests/catch/spidriver/test-spidriver + src/tests/catch/xbee/test-xbee-parser.cpp + src/tests/catch/xbee/test-xbee-driver.cpp [internal-adc] Type: srcfiles @@ -231,6 +254,15 @@ Include: Defines: Main: kernel-testsuite +[mxgui-helloworld] +Type: board +BoardId: stm32f429zi_stm32f4discovery +BinName: mxgui-helloworld +Include: +Defines: -DDEBUG +Libs: libmxgui +Main: examples/mxgui-helloworld + [sdcard-benchmark] Type: board BoardId: stm32f429zi_stm32f4discovery @@ -256,8 +288,8 @@ Main: sdcard-benchmark Type: test BoardId: stm32f429zi_stm32f4discovery BinName: tests-catch -Include: %tests-boardcore %shared %test-utils %spi %sensormanager -Defines: +Include: %tests-boardcore %shared %test-utils %spi %sensormanager %xbee +Defines: -DUSE_MOCK_PERIPHERALS Main: catch/catch-tests-entry [test-kalman] @@ -274,6 +306,7 @@ BoardId: stm32f429zi_stm32f4discovery BinName: test-kalman-eigen Include: %shared Defines: -DDEBUG -DSTANDALONE_CATCH1_TEST +Libs: eigen Main: catch/test-kalman-eigen [test-taskscheduler] @@ -338,6 +371,7 @@ BoardId: stm32f429zi_stm32f4discovery BinName: test-kalman-eigen-benchmark Include: %shared Defines: +Libs: eigen Main: kalman/test-kalman-eigen-benchmark [test-pinobserver] @@ -414,29 +448,29 @@ Include: %shared %gamma868 Defines: -DDEBUG Main: drivers/test-mavlink -[xbee-bitrate] -Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: xbee-bitrate -Include: %shared %xbee %spi -Defines: -DDEBUG -DSDRAM_ISSI -Main: misc/xbee-bitrate +# [xbee-bitrate] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: xbee-bitrate +# Include: %shared %xbee %spi +# Defines: -DDEBUG -DSDRAM_ISSI +# Main: misc/xbee-bitrate -[xbee-send-rcv] -Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: xbee-send-rcv -Include: %shared %xbee %spi -Defines: -DDEBUG -Main: misc/xbee-send-rcv +# [xbee-send-rcv] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: xbee-send-rcv +# Include: %shared %xbee %spi +# Defines: -DDEBUG +# Main: misc/xbee-send-rcv -[xbee-time-to-send] -Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: xbee-time-to-send -Include: %shared %xbee %spi -Defines: -DDEBUG -Main: misc/xbee-time-to-send +# [xbee-time-to-send] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: xbee-time-to-send +# Include: %shared %xbee %spi +# Defines: -DDEBUG +# Main: misc/xbee-time-to-send [test-circularbuffer] Type: test @@ -534,6 +568,39 @@ Include: %shared %spi %ads1118 Defines: -DDEBUG Main: drivers/test-ads1118 +[test-xbee-snd] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-xbee-snd +Include: %shared %spi %xbee %logger +Defines: -DDEBUG +Main: drivers/xbee/test-xbee-snd + +[test-xbee-rcv] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-xbee-rcv +Include: %shared %spi %xbee %logger +Defines: -DDEBUG +Main: drivers/xbee/test-xbee-rcv + +[test-xbee-bidir] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-xbee-bidir +Include: %shared %spi %xbee %logger +Defines: -DDEBUG +Main: drivers/xbee/test-xbee-bidir + +[test-xbee-gui] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-xbee-gui +Include: %shared %spi %xbee %logger src/tests/drivers/xbee/gui/res/respect.cpp +Defines: -DDEBUG +Libs: libmxgui +Main: drivers/xbee/test-xbee-gui + [test-bme280] Type: test BoardId: stm32f407vg_stm32f4discovery @@ -686,4 +753,4 @@ Main: drivers/test-hwtimer-chain # BinName: calibrate-mpu9250 # Include: %shared %sensormanager # Defines: -# Main: drivers/calibrate-mpu9250 \ No newline at end of file +# Main: drivers/calibrate-mpu9250 diff --git a/src/entrypoints/examples/mxgui-helloworld.cpp b/src/entrypoints/examples/mxgui-helloworld.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f2ef202d889c65456bb06645f4d5248af69a138d --- /dev/null +++ b/src/entrypoints/examples/mxgui-helloworld.cpp @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <cstdio> +#include <miosix.h> + +#include <mxgui/entry.h> +#include <mxgui/display.h> + +using namespace mxgui; +using namespace miosix; + +int main() +{ + { + DrawingContext dc(DisplayManager::instance().getDisplay()); + dc.write(Point(0,0),"Hello world"); + } + for(;;) + { + printf("Hello world! Look at the screen!\n"); + Thread::sleep(1000); + } +} \ No newline at end of file diff --git a/src/entrypoints/kernel-testsuite.cpp b/src/entrypoints/kernel-testsuite.cpp index ce71b9421b1a6a3e247a195cfecdd4f3926f9846..dd00dac1a818396f7e32d90d45bef513f4831d53 100644 --- a/src/entrypoints/kernel-testsuite.cpp +++ b/src/entrypoints/kernel-testsuite.cpp @@ -190,6 +190,7 @@ int main() } //For testing mpu unsigned int *m; + (void)m; // Disable unused warning switch(c) { case 't': @@ -347,6 +348,7 @@ int main() case 's': iprintf("Shutting down\n"); shutdown(); + break; default: iprintf("Unrecognized option\n"); } @@ -609,6 +611,7 @@ static volatile bool t1_v1; static void t1_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -701,6 +704,8 @@ static Thread *t2_p_v1; static void t2_p1(void *argv) { + (void)argv; + //This is to fix a race condition: the immediately after the thread //creation a yield occurs, t2_p_v1 is not yet assigned so the check fails Thread::sleep(5); @@ -718,6 +723,7 @@ static void t2_p1(void *argv) static void t2_p2(void *argv) { + (void)argv; while(Thread::testTerminate()==false) t2_v1=true; } @@ -782,6 +788,7 @@ also tests creation of multiple instances of the same thread static void t3_p1(void *argv) { + (void)argv; const int SLEEP_TIME=100; for(;;) { @@ -799,6 +806,7 @@ static volatile bool t3_deleted;//Set when an instance of t3_p2 is deleted static void t3_p2(void *argv) { + (void)argv; const int SLEEP_TIME=15; for(;;) { @@ -893,6 +901,7 @@ static volatile bool t4_v1; static void t4_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1035,6 +1044,7 @@ static volatile bool t5_v2;//False=testing Thread::wait() else Thread::IRQwait() static void t5_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1153,6 +1163,7 @@ static FastMutex t6_m1a; static void t6_p1(void *argv) { + (void)argv; t6_m1.lock(); seq.add('1'); Thread::sleep(100); @@ -1165,6 +1176,7 @@ static void t6_p1(void *argv) static void t6_p2(void *argv) { + (void)argv; t6_m1.lock(); seq.add('2'); Thread::sleep(100); @@ -1176,6 +1188,7 @@ static void t6_p2(void *argv) static void t6_p3(void *argv) { + (void)argv; t6_m1.lock(); seq.add('3'); Thread::sleep(100); @@ -1187,6 +1200,7 @@ static void t6_p3(void *argv) static void t6_p4(void *argv) { + (void)argv; t6_m1.lock(); for(;;) { @@ -1205,6 +1219,7 @@ static void t6_p4(void *argv) static void t6_p1a(void *argv) { + (void)argv; t6_m1a.lock(); seq.add('1'); Thread::sleep(100); @@ -1213,6 +1228,7 @@ static void t6_p1a(void *argv) static void t6_p2a(void *argv) { + (void)argv; t6_m1a.lock(); seq.add('2'); Thread::sleep(100); @@ -1221,6 +1237,7 @@ static void t6_p2a(void *argv) static void t6_p3a(void *argv) { + (void)argv; t6_m1a.lock(); seq.add('3'); Thread::sleep(100); @@ -1229,6 +1246,7 @@ static void t6_p3a(void *argv) static void t6_p4a(void *argv) { + (void)argv; t6_m1a.lock(); for(;;) { @@ -1251,6 +1269,7 @@ static FastMutex t6_m2a; static void t6_p5(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1266,6 +1285,7 @@ static void t6_p5(void *argv) static void t6_p5a(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1305,6 +1325,7 @@ static FastMutex t6_m5a(FastMutex::RECURSIVE); static void *t6_p7(void *argv) { + (void)argv; if(t6_m5.tryLock()==false) return reinterpret_cast<void*>(1); // 1 = locked t6_m5.unlock(); return reinterpret_cast<void*>(0); // 0 = unlocked @@ -1320,6 +1341,7 @@ bool checkIft6_m5IsLocked() static void *t6_p7a(void *argv) { + (void)argv; if(t6_m5a.tryLock()==false) return reinterpret_cast<void*>(1); // 1 = locked t6_m5a.unlock(); return reinterpret_cast<void*>(0); // 0 = unlocked @@ -1741,6 +1763,7 @@ static Queue<char,4> t8_q2; static void t8_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1985,6 +2008,7 @@ void t10_f2() void t10_p1(void *argv) { + (void)argv; t10_f2(); fail("Exception not thrown"); } @@ -2011,6 +2035,7 @@ static volatile unsigned int t11_v1;//Free heap after spawning thread void t11_p1(void *argv) { + (void)argv; if(MemoryProfiling::getStackSize()!=STACK_SMALL) fail("getStackSize (2)"); //Check that getCurrentFreeHeap returns the same value from different //threads if no heap allocation happened in between @@ -2032,7 +2057,8 @@ void test_11() fail("getAbsoluteFreeStack (1)"); if(MemoryProfiling::getAbsoluteFreeStack()>curFreeStack-4) fail("getAbsoluteFreeStack (2)"); - unsigned int heapSize=MemoryProfiling::getHeapSize(); + + unsigned int heapSize=MemoryProfiling::getHeapSize(); if(MemoryProfiling::getCurrentFreeHeap()>heapSize) fail("getCurrentFreeHeap"); if(MemoryProfiling::getAbsoluteFreeHeap()>heapSize) @@ -2057,12 +2083,14 @@ Mutex t12_m2; void t12_p1(void *argv) { + (void)argv; Lock<Mutex> l1(t12_m1); Lock<Mutex> l2(t12_m2); } void t12_p2(void *argv) { + (void)argv; Lock<Mutex> l(t12_m1); } @@ -2265,6 +2293,7 @@ static Mutex t15_m1; void t15_p1(void *argv) { + (void)argv; for(int i=0;i<10;i++) { Lock<Mutex> l(t15_m1); @@ -2276,6 +2305,7 @@ void t15_p1(void *argv) void t15_p2(void *argv) { + (void)argv; for(int i=0;i<10;i++) { Lock<Mutex> l(t15_m1); @@ -2395,6 +2425,7 @@ pthread_cond_t t16_c1=PTHREAD_COND_INITIALIZER; void *t16_p3(void *argv) { + (void)argv; Thread::sleep(30); if(pthread_mutex_trylock(&t16_m1)!=0) fail("cond_wait did not release mutex"); //<--- @@ -2408,6 +2439,7 @@ pthread_cond_t t16_c2; void *t16_p4(void *argv) { + (void)argv; Thread::sleep(30); if(pthread_mutex_trylock(&t16_m1)!=0) fail("cond_wait did not release mutex (2)"); //<--- @@ -2791,6 +2823,7 @@ static void be() static void t19_p1(void *argv) { + (void)argv; Thread::sleep(50); { FastInterruptDisableLock dLock; @@ -3232,6 +3265,7 @@ struct t22_s1 static void t22_t2(void *argv) { + (void)argv; while(Thread::testTerminate()==false) { t22_v5=true; @@ -3958,6 +3992,7 @@ static volatile bool fs_1_error; static void fs_t1_p1(void *argv) { + (void)argv; FILE *f; if((f=fopen("/sd/testdir/file_1.txt","w"))==NULL) { @@ -3990,6 +4025,7 @@ static void fs_t1_p1(void *argv) static void fs_t1_p2(void *argv) { + (void)argv; FILE *f; if((f=fopen("/sd/testdir/file_2.txt","w"))==NULL) { @@ -4022,6 +4058,7 @@ static void fs_t1_p2(void *argv) static void fs_t1_p3(void *argv) { + (void)argv; FILE *f; if((f=fopen("/sd/testdir/file_3.txt","w"))==NULL) { @@ -4558,6 +4595,7 @@ context switch speed static void b2_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; diff --git a/src/shared/ActiveObject.h b/src/shared/ActiveObject.h index f3cd2a415a3e608fa5c29c21b9b14b288a934470..9204180b121e916b0b65a11c39190c19d32c3992 100644 --- a/src/shared/ActiveObject.h +++ b/src/shared/ActiveObject.h @@ -26,6 +26,7 @@ #define SRC_SHARED_ACTIVEOBJECT_H #include <Common.h> + #include "diagnostic/SkywardStack.h" /** @@ -84,9 +85,12 @@ public: */ virtual void stop() { - should_stop = true; - thread->join(); - stopped = true; + if (isRunning()) + { + should_stop = true; + thread->join(); + stopped = true; + } } bool isStarted() { return started; } diff --git a/src/shared/drivers/BusTemplate.h b/src/shared/drivers/BusTemplate.h index 0a9ebfbc6b2fab8322900d69f2bd86067938703e..da58a217e901fcbb647fd470997cfa875d7a0bbb 100644 --- a/src/shared/drivers/BusTemplate.h +++ b/src/shared/drivers/BusTemplate.h @@ -219,7 +219,7 @@ private: return n_spi == 1 || n_spi == 2 ? 5 : 6; } - constexpr SPI_TypeDef* getSPIAddr(unsigned n) + constexpr SPI_TypeDef* getSPIAddr(unsigned n) const { return n == 1 ? SPI1 : n == 2 ? SPI2 : SPI3; } diff --git a/src/shared/drivers/Transceiver.h b/src/shared/drivers/Transceiver.h index 3bfd631a8ee7e878f3ef8293af771ffc3005caaf..fca35311650292d022ce6dff487a496aeb067a46 100644 --- a/src/shared/drivers/Transceiver.h +++ b/src/shared/drivers/Transceiver.h @@ -23,8 +23,6 @@ #ifndef TRANSCEIVER_H #define TRANSCEIVER_H -#include <Common.h> - class Transceiver { public: diff --git a/src/shared/drivers/Xbee/APIFrameParser.cpp b/src/shared/drivers/Xbee/APIFrameParser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ed91fa946236c68d2e6c1f7330fdbe0f63554d68 --- /dev/null +++ b/src/shared/drivers/Xbee/APIFrameParser.cpp @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "APIFrameParser.h" +#include "Debug.h" + +namespace Xbee +{ + +APIFrameParser::APIFrameParser() {} + +APIFrameParser::ParseResult APIFrameParser::parse(uint8_t byte, APIFrame* frame) +{ + switch (parser_state) + { + // Look for the start of frame delimiter + case ParserState::FIND_START: + + if (byte == START_DELIMITER) + { + parser_state = ParserState::READ_LENGTH_1; + } + break; + // Read most significant byte of the length + case ParserState::READ_LENGTH_1: + frame->length = byte; + parser_state = ParserState::READ_LENGTH_2; + + break; + // Read least significant byte of the length + case ParserState::READ_LENGTH_2: + frame->length |= ((uint16_t)byte << 8) & 0xFF00; + // At least two frame data bytes (frame_type and a payload) + if(swapBytes16(frame->length) < 2) + { + parser_state = ParserState::FIND_START; + return ParseResult::FAIL; + } + + if (frame->getFrameDataLength() > FRAME_DATA_SIZE) + { + parser_state = ParserState::FIND_START; + return ParseResult::FAIL; + } + + parser_state = ParserState::READ_FRAME_TYPE; + break; + // Read frame type + case ParserState::READ_FRAME_TYPE: + frame->frame_type = byte; + parser_state = ParserState::READ_FRAME_DATA; + break; + // Read the data frame + case ParserState::READ_FRAME_DATA: + frame->frame_data[current_frame_data_index++] = byte; + + if (current_frame_data_index == frame->getFrameDataLength()) + { + current_frame_data_index = 0; + parser_state = ParserState::READ_CHECKSUM; + } + break; + // Read & verify checksum + case ParserState::READ_CHECKSUM: + frame->checksum = byte; + parser_state = ParserState::FIND_START; + + if (frame->verifyChecksum()) + { + return ParseResult::SUCCESS; + } + else + { + TRACE("[XbeeParser] Wrong packet checksum!\n"); + return ParseResult::FAIL; + } + break; + } + + if (parser_state != ParserState::FIND_START) + { + return ParseResult::PARSING; + } + else + { + return ParseResult::IDLE; + } +} + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/APIFrameParser.h b/src/shared/drivers/Xbee/APIFrameParser.h new file mode 100644 index 0000000000000000000000000000000000000000..d4dcaaa039d3a5a6f946713de4b36a5e9cfb41c3 --- /dev/null +++ b/src/shared/drivers/Xbee/APIFrameParser.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <cstdint> + +#include "APIFrames.h" + +namespace Xbee +{ + +/** + * @brief Parses a byte sequence into an Xbee APIFrame + */ +class APIFrameParser +{ +public: + /** + * @brief Current state of the parser internal state machine + */ + enum class ParserState + { + FIND_START, + READ_LENGTH_1, + READ_LENGTH_2, + READ_FRAME_TYPE, + READ_FRAME_DATA, + READ_CHECKSUM + }; + + /** + * @brief Result of the last parse operation + * + */ + enum class ParseResult : uint8_t + { + IDLE = 0, // No frame has been found yet + PARSING, // Currently paring a frame + SUCCESS, // A frame has been parsed successfully + FAIL // The parsed frame was invalid (eg wrong length, wrong checksum) + }; + + APIFrameParser(); + + /** + * @brief Parses a single byte. When this function returns + * ParseResult:SUCESS, @p frame contains a valid APIFrame + * + * @param byte Byte to parse + * @param frame Frame to be constructed from the parsed data. + * @return ParseResult If SUCCESS is returned, then the provided frame is + * valid and can be used + */ + ParseResult parse(uint8_t byte, APIFrame* frame); + + /** + * @brief Returns the current state of the parser + */ + ParserState getParserState() { return parser_state; } +private: + ParserState parser_state = ParserState::FIND_START; + uint16_t current_frame_data_index = 0; +}; + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/APIFrames.h b/src/shared/drivers/Xbee/APIFrames.h new file mode 100644 index 0000000000000000000000000000000000000000..401b76b9ac6538bb9f04fc0fb7201d73947ae554 --- /dev/null +++ b/src/shared/drivers/Xbee/APIFrames.h @@ -0,0 +1,475 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <cstring> +#include <ostream> +#include <string> +#include <type_traits> +#include <utility> + +#ifndef COMPILE_FOR_X86 +#include <interfaces/endianness.h> +#elif defined(__GNUC__) + +#define swapBytes64 __builtin_bswap64 + +static uint16_t swapBytes16(uint16_t x) +{ + return (x & 0xFF00) >> 8 | (x & 0x00FF) << 8; +} +#endif + +using std::min; +using std::string; + +namespace Xbee +{ +/// Maximum length for a TX/RX frame payload. User-configurable using the "NP" +/// AT command or the Xbee configuration software (see Xbee datasheet) +static constexpr size_t MIN_RX_PACKET_FRAME_SIZE = 11; +static constexpr size_t MIN_TX_REQUEST_FRAME_SIZE = 13; +static constexpr size_t MIN_AT_COMMAND_FRAME_SIZE = 3; +static constexpr size_t MIN_AT_RESPONSE_FRAME_SIZE = 4; + +static constexpr size_t TX_STATUS_FRAME_SIZE = 6; +static constexpr size_t MODEM_STATUS_FRAME_SIZE = 1; + +static constexpr uint16_t MAX_PACKET_PAYLOAD_LENGTH = 256; +static constexpr uint16_t MAX_AT_COMMAND_PARAMS_LENGTH = 20; +static constexpr uint16_t MAX_AT_COMMAND_RESPONSE_LENGTH = 30; + +static constexpr size_t FRAME_DATA_SIZE = + MAX_PACKET_PAYLOAD_LENGTH + MIN_TX_REQUEST_FRAME_SIZE; + +static constexpr size_t MAX_API_FRAME_SIZE = FRAME_DATA_SIZE + 5; +static constexpr size_t MIN_API_FRAME_SIZE = 5; + +static constexpr uint64_t ADDRESS_BROADCAST = 0xFFFF; +static constexpr uint8_t START_DELIMITER = 0x7E; + +enum FrameType : uint8_t +{ + FTYPE_AT_COMMAND = 0x08, + FTYPE_AT_COMMAND_QUEUE = 0x09, + FTYPE_TX_REQUEST = 0x10, + FTYPE_AT_COMMAND_RESPONSE = 0x88, + FTYPE_MODEM_STATUS = 0x8A, + FTYPE_TX_STATUS = 0x8B, + FTYPE_RX_PACKET_FRAME = 0x90, +}; + +enum TransmitOptionsBitfield : uint8_t +{ + TO_DISABLE_ACK = 0x01, + TO_DISABLE_RD = 0x02, + TO_NACK = 0x04, + TO_TRACE_ROUTE = 0x08, + + TO_DM_POINT_MULTIPOINT = 0x40, + TO_DM_REPEATER_MODE = 0x80, + TO_DM_DIGIMESH = 0xC0 +}; + +enum CommandStatusBitfield : uint8_t +{ + CS_OK = 0x00, + CS_ERROR = 0x01, + CS_INVALID_COMMAND = 0x02, + CS_INVALID_PARAMETER = 0x03, + CS_RSSI_INVALID = 0x40, + CS_IS_REMOTE_COMMAND = 0x80 +}; + +enum ModemStatus : uint8_t +{ + MS_HARDWARE_RESET = 0x00, + MS_WATCHDOG_TIMER_RESET = 0x01, + MS_NETWORK_WOKE_UP = 0x0B, + MS_NETWORK_WENT_SLEEP = 0x0C, +}; + +enum DeliveryStatus : uint8_t +{ + DELS_SUCCESS = 0x00, + DELS_MAC_ACK_FAILURE = 0x01, + DELS_COLL_AVOID_FAILURE = 0x02, + DELS_NO_SPECTRUM_AVAILABLE = 0x03, + DELS_NET_ACK_FAILURE = 0x21, + DELS_ROUTE_NOT_FOUND = 0x25, + DELS_INT_RESOURCE_ERR = 0x31, + DELS_INTERNAL_ERROR = 0x32, + DELS_PAYLOAD_TOO_LARGE = 0x74, + DELS_INDIRECT_MSG_REQ = 0x75, +}; + +enum DiscoveryStatus : uint8_t +{ + DISCS_NO_DISC_OVERHEAD = 0x00, + DISCS_ROUTE_DISCOVERY = 0x02 +}; + +enum ReceiveOptions : uint8_t +{ + RO_PACKET_ACK = 0x00, + RO_PACKET_IS_BROADCAST = 0x01, + RO_POINT_MULTIPOINT = 0x40, + RO_REPEATER_MODE = 0x80, + RO_DIGIMESH = 0xC0, +}; + +#pragma pack(1) +struct APIFrame +{ + uint8_t start_del = 0x7E; + uint16_t length = 0; + uint8_t frame_type = 0; + uint8_t frame_data[FRAME_DATA_SIZE]; + + uint8_t checksum = 0; + // Used for logging, not part of the standard Xbee API Frame + long long timestamp = 0; + + APIFrame() { memset(frame_data, 0, FRAME_DATA_SIZE); } + + uint16_t getFrameDataLength() const + { + size_t len = swapBytes16(length) - 1; + assert(len <= FRAME_DATA_SIZE); + + return min(len, FRAME_DATA_SIZE); + } + + void setFrameDataLength(uint16_t len) + { + assert(len <= FRAME_DATA_SIZE); + length = swapBytes16(min((size_t)(len + 1), FRAME_DATA_SIZE + 1)); + } + + bool verifyChecksum() const + { + assert(getFrameDataLength() <= FRAME_DATA_SIZE); + // Sum all the bytes including checksum and frame type. + // The sum can be stored in a uint8_t since we only care about the least + // significant byte. + uint8_t sum = checksum + frame_type; + for (uint16_t i = 0; i < getFrameDataLength(); ++i) + { + sum += frame_data[i]; + } + return sum == 0xFF; + } + + void calcChecksum() + { + assert(getFrameDataLength() <= FRAME_DATA_SIZE); + checksum = frame_type; + for (uint16_t i = 0; i < getFrameDataLength(); ++i) + { + checksum += frame_data[i]; + } + + checksum = 0xFF - checksum; + } + + template <typename FrameType> + FrameType* toFrameType() + { + static_assert( + std::is_base_of<APIFrame, FrameType>::value || + std::is_same<APIFrame, FrameType>::value, + "FrameType must be derived from APIFrame or be an APIFrame"); + + return reinterpret_cast<FrameType*>(this); + } + + size_t toBytes(uint8_t* bytes) + { + memcpy(bytes, this, 4 + getFrameDataLength()); + + bytes[4 + getFrameDataLength()] = checksum; + return 5 + getFrameDataLength(); + } + + static bool fromBytes(uint8_t* bytes, size_t size, APIFrame* f) + { + if (size >= MIN_API_FRAME_SIZE && size <= MAX_API_FRAME_SIZE) + { + memcpy(f, bytes, size - 1); + f->checksum = bytes[size - 1]; + + return true; + } + return false; + } +}; +#pragma pack() +struct ATCommandFrame : public APIFrame +{ + ATCommandFrame() : APIFrame() + { + frame_type = FTYPE_AT_COMMAND; + setFrameDataLength(MIN_AT_COMMAND_FRAME_SIZE); + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + const char* getATCommand() const + { + return reinterpret_cast<const char*>(&frame_data[1]); + } + + void setATCommand(const char* at) + { + frame_data[1] = at[0]; + frame_data[2] = at[1]; + } + + uint8_t* getCommandDataPointer() { return &frame_data[3]; } + + uint16_t getCommandDataLength() const + { + return getFrameDataLength() - MIN_AT_COMMAND_FRAME_SIZE; + } + + void setParameterSize(uint16_t size) + { + assert(size <= FRAME_DATA_SIZE - MIN_AT_COMMAND_FRAME_SIZE); + size = min((size_t)size, FRAME_DATA_SIZE - MIN_AT_COMMAND_FRAME_SIZE); + + setFrameDataLength(MIN_AT_COMMAND_FRAME_SIZE + size); + } +}; +static_assert(sizeof(ATCommandFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct ATCommandResponseFrame : public APIFrame +{ + ATCommandResponseFrame() : APIFrame() + { + frame_type = FTYPE_AT_COMMAND_RESPONSE; + setFrameDataLength(MIN_AT_RESPONSE_FRAME_SIZE); + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + const char* getATCommand() const + { + return reinterpret_cast<const char*>(&frame_data[1]); + } + + void setATCommand(const char* at) + { + frame_data[1] = at[0]; + frame_data[2] = at[1]; + } + + uint8_t getCommandStatus() const { return frame_data[3]; } + + void setCommandStatus(uint8_t cs) { frame_data[3] = cs; } + + uint8_t* getCommandDataPointer() { return &frame_data[4]; } + + uint16_t getCommandDataLength() const + { + return getFrameDataLength() - MIN_AT_RESPONSE_FRAME_SIZE; + } + + void setCommandDataSize(uint16_t size) + { + assert(size <= FRAME_DATA_SIZE - MIN_AT_RESPONSE_FRAME_SIZE); + size = min((size_t)size, FRAME_DATA_SIZE - MIN_AT_RESPONSE_FRAME_SIZE); + + setFrameDataLength(MIN_AT_RESPONSE_FRAME_SIZE + size); + } +}; +static_assert(sizeof(ATCommandFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct TXRequestFrame : public APIFrame +{ + TXRequestFrame() : APIFrame() + { + frame_type = FTYPE_TX_REQUEST; + setFrameDataLength(MIN_TX_REQUEST_FRAME_SIZE); + + // Reserved bytes + frame_data[9] = 0xFF; + frame_data[10] = 0xFE; + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + uint64_t getDestAddress() const + { + uint64_t addr; + memcpy(&addr, &frame_data[1], sizeof(uint64_t)); + return swapBytes64(addr); + } + + void setDestAddress(uint64_t address) + { + address = swapBytes64(address); + memcpy(&frame_data[1], &address, 8); + } + + uint8_t getBroadcastRadius() const { return frame_data[11]; } + + void setBroadcastRadius(uint8_t br) { frame_data[11] = br; } + + uint8_t getTrasmitOptions() const { return frame_data[12]; } + + void setTransmitOptions(uint8_t br) { frame_data[12] = br; } + + uint8_t* getRFDataPointer() { return &frame_data[13]; } + + uint16_t getRFDataLength() const + { + return min((size_t)(getFrameDataLength() - MIN_TX_REQUEST_FRAME_SIZE), + (size_t)MAX_PACKET_PAYLOAD_LENGTH); + } + + void setRFDataLength(uint16_t size) + { + assert(size <= MAX_PACKET_PAYLOAD_LENGTH); + size = min(size, MAX_PACKET_PAYLOAD_LENGTH); + + setFrameDataLength(MIN_TX_REQUEST_FRAME_SIZE + size); + } +}; +static_assert(sizeof(TXRequestFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct ModemStatusFrame : public APIFrame +{ + ModemStatusFrame() : APIFrame() + { + frame_type = FTYPE_MODEM_STATUS; + setFrameDataLength(MODEM_STATUS_FRAME_SIZE); + } + + uint8_t getStatus() const { return frame_data[0]; } + + void setStatus(uint8_t status) { frame_data[0] = status; } +}; +static_assert(sizeof(ModemStatusFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct TXStatusFrame : public APIFrame +{ + TXStatusFrame() : APIFrame() + { + frame_type = FTYPE_TX_STATUS; + setFrameDataLength(TX_STATUS_FRAME_SIZE); + + // Reserved bytes + frame_data[1] = 0xFF; + frame_data[2] = 0xFE; + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + uint8_t getTransmitRetryCount() const { return frame_data[3]; } + + void setTransmitRetryCount(uint8_t trc) { frame_data[3] = trc; } + + uint8_t getDeliveryStatus() const { return frame_data[4]; } + + void setDeliveryStatus(uint8_t ds) { frame_data[4] = ds; } + + uint8_t getDiscoveryStatus() const { return frame_data[5]; } + + void setDiscoveryStatus(uint8_t ds) { frame_data[5] = ds; } +}; + +static_assert(sizeof(TXStatusFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct RXPacketFrame : public APIFrame +{ + RXPacketFrame() : APIFrame() + { + frame_type = FTYPE_RX_PACKET_FRAME; + setFrameDataLength(MIN_RX_PACKET_FRAME_SIZE); + + frame_data[8] = 0xFF; + frame_data[9] = 0xFE; + } + + uint64_t getSourceAddress() const + { + uint64_t addr; + memcpy(&addr, &frame_data[0], sizeof(uint64_t)); + return swapBytes64(addr); + } + + void setSourceAddress(uint64_t address) + { + address = swapBytes64(address); + uint8_t* addr = reinterpret_cast<uint8_t*>(&address); + + memcpy(&frame_data[0], addr, 8); + } + + uint8_t getReceiveOptions() const { return frame_data[10]; } + + void setReceiveOptions(uint8_t ro) { frame_data[10] = ro; } + + uint8_t* getRXDataPointer() { return &frame_data[11]; } + + uint16_t getRXDataLength() const + { + return min((size_t)(getFrameDataLength() - MIN_RX_PACKET_FRAME_SIZE), + (size_t)MAX_PACKET_PAYLOAD_LENGTH); + } + + void setRXDataLength(uint16_t size) + { + assert(size <= MAX_PACKET_PAYLOAD_LENGTH); + size = min(size, MAX_PACKET_PAYLOAD_LENGTH); + + setFrameDataLength(MIN_RX_PACKET_FRAME_SIZE + size); + } +}; + +static_assert(sizeof(RXPacketFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/APIFramesLog.h b/src/shared/drivers/Xbee/APIFramesLog.h new file mode 100644 index 0000000000000000000000000000000000000000..d97736f8a38fa9b5a9a34c78820b96008763247b --- /dev/null +++ b/src/shared/drivers/Xbee/APIFramesLog.h @@ -0,0 +1,394 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <algorithm> +#include <cstdint> +#include <ostream> +#include <string> +#include <utility> + +#include "APIFrames.h" + +using std::min; + +/** + * Classes used if you want to log an APIFrame, but do not want to log the base + * APIFrame class, which is quite heavy on memory + */ + +namespace Xbee +{ + +struct APIFrameLog +{ + long long timestamp; + uint8_t frame_type; + uint16_t frame_data_length; + uint8_t frame_data[FRAME_DATA_SIZE]; + + static bool fromAPIFrame(APIFrame& api, APIFrameLog* dest) + { + dest->timestamp = api.timestamp; + dest->frame_type = api.frame_type; + dest->frame_data_length = api.getFrameDataLength(); + + memcpy(dest->frame_data, api.frame_data, dest->frame_data_length); + + return true; + } + + static string header() { return "timestamp,length,frame_type\n"; } + + void print(std::ostream& os) const + { + os << timestamp << "," << frame_data_length << "," << frame_type + << "\n"; + } +}; + +struct ATCommandFrameLog +{ + long long timestamp; + uint8_t frame_id = 0; + char at_command[2]; + + uint8_t command_data[MAX_AT_COMMAND_PARAMS_LENGTH]; + uint16_t command_data_length = 0; + + static bool toFrameType(APIFrame& api, ATCommandFrameLog* dest) + { + if (api.frame_type != FTYPE_AT_COMMAND) + { + return false; + } + + if (api.getFrameDataLength() < MIN_AT_COMMAND_FRAME_SIZE || + api.getFrameDataLength() > + MIN_AT_COMMAND_FRAME_SIZE + MAX_AT_COMMAND_PARAMS_LENGTH) + { + return false; + } + ATCommandFrame* at = api.toFrameType<ATCommandFrame>(); + + dest->timestamp = at->timestamp; + dest->frame_id = at->getFrameID(); + + memcpy(dest->at_command, at->getATCommand(), 2); + + dest->command_data_length = at->getCommandDataLength(); + memcpy(dest->command_data, at->getCommandDataPointer(), + dest->command_data_length); + + return true; + } + + static string header() { return "timestamp,id,cmd,param_size,params\n"; } + + void print(std::ostream& os) const + { + char cmd[3]; + + strncpy(cmd, at_command, 2); + cmd[2] = '\0'; + + os << timestamp << "," << (int)frame_id << "," << cmd << "," + << command_data_length << ","; + + if (command_data_length > 0) + { + for (uint16_t i = 0; i < command_data_length; i++) + { + os << (int)command_data[i] << " "; + } + } + else + { + os << "-"; + } + + os << "\n"; + } +}; + +struct TXRequestFrameLog +{ + long long timestamp = 0; + uint8_t frame_id = 0; + + uint64_t dest_address = 0; + + uint8_t broadcast_radius = 0; + uint8_t transmit_options = 0; + + uint8_t rf_data[MAX_PACKET_PAYLOAD_LENGTH]; + uint16_t rf_data_length = 0; + + static bool toFrameType(APIFrame& api, TXRequestFrameLog* dest) + { + if (api.frame_type != FTYPE_TX_REQUEST) + { + return false; + } + + if (api.getFrameDataLength() < MIN_TX_REQUEST_FRAME_SIZE || + api.getFrameDataLength() > + MIN_TX_REQUEST_FRAME_SIZE + MAX_PACKET_PAYLOAD_LENGTH) + { + return false; + } + + TXRequestFrame* tx = api.toFrameType<TXRequestFrame>(); + + dest->timestamp = api.timestamp; + dest->frame_id = tx->getFrameID(); + + dest->dest_address = tx->getDestAddress(); + + dest->broadcast_radius = tx->getBroadcastRadius(); + dest->transmit_options = tx->getTrasmitOptions(); + + dest->rf_data_length = tx->getRFDataLength(); + memcpy(dest->rf_data, tx->getRFDataPointer(), dest->rf_data_length); + + return true; + } + + static string header() + { + return "timestamp,id,dest_addr,broadcast_radius,tx_options,rf_data_" + "len,first_uint32\n"; + } + + void print(std::ostream& os) const + { + uint32_t d; + memcpy(&d, rf_data, sizeof(d)); + os << timestamp << "," << (int)frame_id << "," << dest_address << "," + << (int)broadcast_radius << "," << (int)transmit_options << "," + << rf_data_length << "," << d << "\n"; + } +}; + +struct ATCommandResponseFrameLog +{ + long long timestamp = 0; + uint8_t frame_id = 0; + char at_command[2]; + uint8_t command_status = 0; + + uint8_t command_data[MAX_AT_COMMAND_RESPONSE_LENGTH]; + uint16_t command_data_length = 0; + + static bool toFrameType(APIFrame& api, ATCommandResponseFrameLog* dest) + { + if (api.frame_type != FTYPE_AT_COMMAND_RESPONSE) + { + return false; + } + + if (api.getFrameDataLength() < MIN_AT_COMMAND_FRAME_SIZE || + api.getFrameDataLength() > + MIN_AT_COMMAND_FRAME_SIZE + MAX_AT_COMMAND_RESPONSE_LENGTH) + { + return false; + } + + ATCommandResponseFrame* at = api.toFrameType<ATCommandResponseFrame>(); + + dest->timestamp = api.timestamp; + dest->frame_id = at->getFrameID(); + memcpy(dest->at_command, at->getATCommand(), 2); + + dest->command_status = at->getCommandStatus(); + + dest->command_data_length = at->getCommandDataLength(); + memcpy(dest->command_data, at->getCommandDataPointer(), + dest->command_data_length); + + return true; + } + + static string header() + { + return "timestamp,id,cmd,status,param_size,params\n"; + } + + void print(std::ostream& os) const + { + char cmd[3]; + + strncpy(cmd, at_command, 2); + cmd[2] = '\0'; + + os << timestamp << "," << (int)frame_id << "," << cmd << "," + << (int)command_status << "," << command_data_length << ","; + + if (command_data_length > 0) + { + for (uint16_t i = 0; i < command_data_length; i++) + { + os << (int)command_data[i] << " "; + } + } + else + { + os << "-"; + } + + os << "\n"; + } +}; + +struct ModemStatusFrameLog +{ + long long timestamp = 0; + uint8_t modem_status = 0; + + static bool toFrameType(APIFrame& api, ModemStatusFrameLog* dest) + { + if (api.frame_type != FTYPE_MODEM_STATUS) + { + return false; + } + + if (api.getFrameDataLength() != MODEM_STATUS_FRAME_SIZE) + { + return false; + } + + ModemStatusFrame* modem = api.toFrameType<ModemStatusFrame>(); + + dest->timestamp = api.timestamp; + dest->modem_status = modem->getStatus(); + + return true; + } + + static string header() { return "timestamp,status\n"; } + + void print(std::ostream& os) const + { + os << timestamp << "," << (int)modem_status << "\n"; + } +}; + +struct TXStatusFrameLog +{ + long long timestamp = 0; + uint8_t frame_id = 0; + uint8_t tx_retry_count = 0; + uint8_t delivery_status = 0; + uint8_t discovery_status = 0; + + static bool toFrameType(APIFrame& api, TXStatusFrameLog* dest) + { + if (api.frame_type != FTYPE_TX_STATUS) + { + return false; + } + + if (api.getFrameDataLength() != TX_STATUS_FRAME_SIZE) + { + return false; + } + + TXStatusFrame* tx = api.toFrameType<TXStatusFrame>(); + + dest->timestamp = api.timestamp; + dest->frame_id = tx->getFrameID(); + + dest->tx_retry_count = tx->getTransmitRetryCount(); + dest->delivery_status = tx->getDeliveryStatus(); + dest->discovery_status = tx->getDiscoveryStatus(); + + return true; + } + + static string header() + { + return "timestamp,id,tx_retries,delivery_status,discovery_status\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << (int)frame_id << "," << (int)tx_retry_count + << "," << (int)delivery_status << "," << (int)discovery_status + << "\n"; + } +}; + +struct RXPacketFrameLog +{ + long long timestamp = 0; + + uint64_t source_address = 0; + + uint8_t receive_options = 0; + + uint8_t rx_data[MAX_PACKET_PAYLOAD_LENGTH]; + uint16_t rx_data_length = 0; + + static bool toFrameType(APIFrame& api, RXPacketFrameLog* dest) + { + if (api.frame_type != FTYPE_RX_PACKET_FRAME) + { + return false; + } + + if (api.getFrameDataLength() < MIN_RX_PACKET_FRAME_SIZE || + api.getFrameDataLength() > + MIN_RX_PACKET_FRAME_SIZE + MAX_PACKET_PAYLOAD_LENGTH) + { + return false; + } + + RXPacketFrame* rx = api.toFrameType<RXPacketFrame>(); + + dest->timestamp = api.timestamp; + dest->source_address = rx->getSourceAddress(); + + dest->receive_options = rx->getReceiveOptions(); + + dest->rx_data_length = rx->getRXDataLength(); + memcpy(dest->rx_data, rx->getRXDataPointer(), dest->rx_data_length); + + return true; + } + + static string header() + { + return "timestamp,src_addr,rx_options,rx_data_size,payload32_0,payload32_1\n"; + } + + void print(std::ostream& os) const + { + uint32_t data[2]; + memcpy(&data, rx_data, min(sizeof(uint32_t)*2, (size_t)rx_data_length)); + os << timestamp << "," << source_address << "," << (int)receive_options + << "," << rx_data_length << "," << data[0] << "," << data[1] << "\n"; + } +}; + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/ATCommands.h b/src/shared/drivers/Xbee/ATCommands.h new file mode 100644 index 0000000000000000000000000000000000000000..09280f9b74ff1b46c370019d921ecaf7ba8e1eed --- /dev/null +++ b/src/shared/drivers/Xbee/ATCommands.h @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "APIFrames.h" +#include "Xbee.h" + +namespace Xbee +{ + +/** + * @brief Enables or disable the specified channels. Channel 9 and 24 are always + * disabled due to regulatory limitations. + * See datasheet for the list of channel frequencies. + * + * @param xbee Reference to an xbee object + * @param channels channel mask + * @return True if the command was executed successfully + */ +bool setChannelMask(Xbee& xbee, uint32_t mask, unsigned int timeout = 1000) +{ + ATCommandResponseFrame response; + + mask = swapBytes32(mask); + + return xbee.sendATCommand("CM", &response, + reinterpret_cast<uint8_t*>(&mask), 4, timeout); +} + +/** + * @brief Enables or disable the specified channels. Channel 9 and 24 are always + * disabled due to regulatory limitations. + * See datasheet for the list of channel frequencies. + * + * @param xbee Reference to an xbee object + * @param channels channel mask array + * + * @return True if the command was executed successfully + */ +bool setChannelMask(Xbee& xbee, bool channels[30], unsigned int timeout = 1000) +{ + uint32_t val = 0; + + for (int i = 0; i < 30; i++) + { + val &= ((uint32_t)channels[i]) << i; + } + + return setChannelMask(xbee, val, timeout); +} + +/** + * @brief Enable communication on all available channels. Equivalent to calling + * setChannelMask(xbee, 0x3EFFFDFF). + * + * @param xbee Reference to an xbee object + * @return True if the command was executed successfully + */ +bool enableAllChannels(Xbee& xbee, unsigned int timeout = 1000) +{ + return setChannelMask(xbee, 0x3EFFFDFF, timeout); +} + +/** + * @brief Disables frequency hopping by allowing communication only on channel + * #29. Equivalent to calling setChannelMask(xbee, 0x20000000). + * + * @param xbee Reference to an xbee object + */ +bool disableFrequencyHopping(Xbee& xbee, unsigned int timeout = 1000) +{ + return setChannelMask(xbee, 0x20000000, timeout); +} + +/** + * @brief Configures the desired xbee data rate. + * + * @param xbee Reference to an xbee object + * @param data_rate_80kbps true for 80kbps, false for 10kbps + * @return True if the command was executed successfully + */ +bool setDataRate(Xbee& xbee, bool data_rate_80kbps, unsigned int timeout = 1000) +{ + uint8_t param = (uint8_t)data_rate_80kbps; + ATCommandResponseFrame response; + + return xbee.sendATCommand("BR", &response, ¶m, 1, timeout); +} + +/** + * @brief Writes parameter values to non-volatile memory so that parameter + * modifications persist through subsequent resets. + * + * @param xbee Reference to an xbee object + * @return True if the command was executed successfully + */ +bool writeToMemory(Xbee& xbee, unsigned int timeout = 1000) +{ + ATCommandResponseFrame response; + return xbee.sendATCommand("WR", &response, nullptr, 0, timeout); +} + +/** + * @brief Performs an energy detect scan on all channels + * + * @param xbee Reference to an xbee object + * @param energy_detect_data Pointer to a 30-bytes buffer where energy levels + * will be stored. Energy levels are in -dBm units. + * @param duration Scan duration in ms + * @return True if the command was executed successfully and data stored in + * energy_detect_data is valid + */ +bool energyDetect(Xbee& xbee, int* energy_detect_data, uint8_t duration, + unsigned int timeout = 1000) +{ + ATCommandResponseFrame response; + if (xbee.sendATCommand("ED", &response, &duration, 1, timeout)) + { + uint16_t resp_len = response.getCommandDataLength(); + if (resp_len == 30) + { + for (int i = 0; i < 30; i++) + { + energy_detect_data[i] = + (int)(*(response.getCommandDataPointer() + i)); + } + + return true; + } + } + return false; +} + +} // namespace Xbee diff --git a/src/shared/drivers/Xbee/Xbee.cpp b/src/shared/drivers/Xbee/Xbee.cpp index 4cc53fb6b2ba165fd28aa69da0a2877b28f2f986..965f158edab37589f0082608a1bb5a592e3f0683 100644 --- a/src/shared/drivers/Xbee/Xbee.cpp +++ b/src/shared/drivers/Xbee/Xbee.cpp @@ -1,17 +1,17 @@ /** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,7 +23,512 @@ #include "Xbee.h" +#include <Debug.h> +#include <kernel/scheduler/scheduler.h> +#include <miosix.h> + +#include <algorithm> + +using miosix::FastMutex; +using miosix::Lock; +using miosix::Unlock; +using std::min; + namespace Xbee { -miosix::Thread* waiting = nullptr; -} \ No newline at end of file + +Xbee::Xbee(SPIBusInterface& bus, GpioType cs, GpioType attn, GpioType rst, + long long tx_timeout) + : Xbee(bus, {}, cs, attn, rst, tx_timeout) +{ + spi_xbee.config.clock_div = SPIClockDivider::DIV128; +} + +Xbee::Xbee(SPIBusInterface& bus, SPIBusConfig config, GpioType cs, + GpioType attn, GpioType rst, long long tx_timeout) + : spi_xbee(bus, cs, config), attn(attn), rst(rst), tx_timeout(tx_timeout) +{ + reset(); +} + +Xbee::~Xbee() { wakeReceiver(true); } + +bool Xbee::send(uint8_t* pkt, size_t pkt_len) +{ + if (pkt_len > MAX_PACKET_PAYLOAD_LENGTH || pkt_len == 0) + { + TRACE("[Xbee] Invalid packet length (0< %u <= %u)\n", pkt_len, + MAX_PACKET_PAYLOAD_LENGTH); + return false; + } + long long start_tick = miosix::getTick(); + + TXRequestFrame tx_req; + uint8_t tx_frame_id = buildTXRequestFrame(tx_req, pkt, pkt_len); + bool success = false; + bool status_timeout = true; + { + Lock<FastMutex> l(mutex_xbee_comm); + writeFrame(tx_req); + + // Wait for a TX Status frame + long long timeout_tick = miosix::getTick() + tx_timeout; + + while (waitForFrame(FTYPE_TX_STATUS, FRAME_POLL_INTERVAL, timeout_tick)) + { + TXStatusFrame* f = parsing_api_frame.toFrameType<TXStatusFrame>(); + + if (f->getFrameID() == tx_frame_id) + { + success = f->getDeliveryStatus() == DELS_SUCCESS; + status_timeout = false; + break; + } + else + { + TRACE("[Xbee] Wrong tx_status ID\n"); + } + } + + // Ak for total transmitted byte count for logging purposes + sendATCommandInternal("BC"); + } + + // If there is more data to receive, wake the receive() thread + if (attn.value() == 0) + { + wakeReceiver(); + } + if (status_timeout) + { + ++status.tx_timeout_count; + TRACE("[Xbee] TX_STATUS timeout\n"); + } + time_to_send_stats.add(miosix::getTick() - start_tick); + return success; +} + +ssize_t Xbee::receive(uint8_t* buf, size_t buf_max_size) +{ + while (true) + { + // We have data in the buffer pending to be returned + if (!rx_frames_buf.isEmpty() || curr_rx_payload_ptr >= 0) + { + return fillReceiveBuf(buf, buf_max_size); + } + // No data in the buffer, but the xbee has data to return via SPI + else if (attn.value() == 0) + { + Lock<FastMutex> l(mutex_xbee_comm); + if (readRXFrame()) + { + sendATCommandInternal("DB"); // Query last packet RSSI + sendATCommandInternal("ER"); // Query receive error count + } + } + // No data available at all: sleep until we have something to do + else + { + { + miosix::FastInterruptDisableLock dLock; + receive_thread = miosix::Thread::getCurrentThread(); + + while (receive_thread != 0) // Avoid spurious wakeups + { + receive_thread->IRQwait(); + { + miosix::FastInterruptEnableLock eLock(dLock); + miosix::Thread::yield(); + } + } + } + + // Forcefully return without receiving anything + if (force_rcv_return) + { + force_rcv_return = false; + return -1; + } + } + } +} + +XbeeStatus Xbee::getStatus() +{ + status.timestamp = miosix::getTick(); + status.time_to_send_stats = time_to_send_stats.getStats(); + return status; +} + +void Xbee::reset() +{ + Lock<FastMutex> l(mutex_xbee_comm); + { + miosix::FastInterruptDisableLock dLock(); + rst.mode(miosix::Mode::OPEN_DRAIN); + } + rst.low(); + miosix::delayUs(50); + rst.high(); + + // When the xbee is ready, we should assert SSEL to tell it to use + // SPI, and it should provide us with a modem status frame + long long timeout = miosix::getTick() + 1000; + do + { + // Assert SSEL on every iteration as we don't exactly know when the + // xbee will be ready. + { + SPIAcquireLock acq(spi_xbee); + spi_xbee.cs.low(); + miosix::delayUs(10); + spi_xbee.cs.high(); + } + + miosix::delayUs(50); + + if (attn.value() == 0 && readOneFrame() == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + + if (parsing_api_frame.frame_type == FTYPE_MODEM_STATUS) + { + break; + } + } + miosix::Thread::sleep(5); + } while (miosix::getTick() < timeout); +} + +void Xbee::wakeReceiver(bool force_return) +{ + force_rcv_return = force_return; + miosix::FastInterruptDisableLock dLock; + + if (receive_thread) + { + receive_thread->IRQwakeup(); + receive_thread = 0; + } +} + +void Xbee::handleATTNInterrupt() +{ + if (receive_thread) + { + receive_thread->IRQwakeup(); + if (receive_thread->IRQgetPriority() > + miosix::Thread::IRQgetCurrentThread()->IRQgetPriority()) + { + miosix::Scheduler::IRQfindNextThread(); + } + receive_thread = 0; + } +} + +size_t Xbee::fillReceiveBuf(uint8_t* buf, size_t buf_max_size) +{ + if (!rx_frames_buf.isEmpty() && curr_rx_payload_ptr == -1) + { + Lock<FastMutex> l(mutex_rx_frames); + curr_rx_frame = rx_frames_buf.pop(); + curr_rx_payload_ptr = 0; + } + + if (curr_rx_payload_ptr >= 0) + { + size_t len_remaining_data = + curr_rx_frame.getRXDataLength() - curr_rx_payload_ptr; + + // The buffer may be smaller than the data we need to return + size_t len_to_copy = min(buf_max_size, len_remaining_data); + + memcpy(buf, curr_rx_frame.getRXDataPointer() + curr_rx_payload_ptr, + len_to_copy); + + curr_rx_payload_ptr += len_to_copy; + + if (curr_rx_payload_ptr == curr_rx_frame.getRXDataLength()) + { + // We've emptied the current frame + curr_rx_payload_ptr = -1; + } + + return len_to_copy; + } + + + return 0; +} + +ParseResult Xbee::readOneFrame() +{ + SPIAcquireLock acq(spi_xbee); + SPISelectLock sel(spi_xbee); + + ParseResult result = ParseResult::IDLE; + do + { + result = parser.parse(spi_xbee.bus.read(), &parsing_api_frame); + } while (attn.value() == 0 && result == ParseResult::PARSING); + + return result; +} + +bool Xbee::readRXFrame() +{ + while (attn.value() == 0) + { + if (readOneFrame() == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + + if (parsing_api_frame.frame_type == FTYPE_RX_PACKET_FRAME) + { + return true; + } + } + } + return false; +} + +void Xbee::writeFrame(APIFrame& frame) +{ + frame.timestamp = miosix::getTick(); // Only for logging purposes + + // Serialize the frame + uint8_t tx_buf[MAX_API_FRAME_SIZE]; + size_t tx_buf_size = frame.toBytes(tx_buf); + + { + SPITransaction spi(spi_xbee); + spi.transfer(tx_buf, tx_buf_size); + } + + // Pass the frame we just sent to the listener + if (frame_listener) + { + frame_listener(frame); + } + + // Full duplex spi transfer: we may have received valid data while we + // were sending the frame. + for (unsigned int i = 0; i < tx_buf_size; i++) + { + ParseResult res = parser.parse(tx_buf[i], &parsing_api_frame); + if (res == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + } + } +} + +bool Xbee::waitForFrame(uint8_t frame_type, unsigned int poll_interval, + long long timeout_tick) +{ + do + { + if (attn.value() == 0) + { + if (readOneFrame() == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + + if (parsing_api_frame.frame_type == frame_type) + { + return true; + } + } + } + else + { + miosix::Thread::sleep(poll_interval); + } + } while (miosix::getTick() < timeout_tick); + + return false; +} + +uint8_t Xbee::buildTXRequestFrame(TXRequestFrame& tx_req, uint8_t* pkt, + size_t pkt_len) +{ + memcpy(tx_req.getRFDataPointer(), pkt, pkt_len); + tx_req.setRFDataLength(pkt_len); + + uint8_t tx_frame_id = getNewFrameID(); + + tx_req.setFrameID(tx_frame_id); + + tx_req.setDestAddress(ADDRESS_BROADCAST); + tx_req.setBroadcastRadius(0); // 0 = max hops, but we don't really care as + // it does not apply to point-multipoint mode + // Point-multipoint mode, disable ack, disable route discovery + tx_req.setTransmitOptions(TO_DM_POINT_MULTIPOINT | TO_DISABLE_ACK | + TO_DISABLE_RD); + tx_req.calcChecksum(); + + return tx_frame_id; +} + +void Xbee::sendATCommand(const char* cmd, uint8_t* params, size_t params_len) +{ + Lock<FastMutex> l(mutex_xbee_comm); + + sendATCommandInternal(0, cmd, params, params_len); +} + +bool Xbee::sendATCommand(const char* cmd, ATCommandResponseFrame* response, + uint8_t* params, size_t params_len, + unsigned int timeout) +{ + Lock<FastMutex> l(mutex_xbee_comm); + + uint8_t tx_frame_id = sendATCommandInternal(cmd, params, params_len); + + bool success = false; + + long long timeout_tick = miosix::getTick() + timeout; + + while (waitForFrame(FTYPE_AT_COMMAND_RESPONSE, FRAME_POLL_INTERVAL, + timeout_tick)) + { + ATCommandResponseFrame* f = + parsing_api_frame.toFrameType<ATCommandResponseFrame>(); + + if (f->getFrameID() == tx_frame_id && + strncmp(cmd, f->getATCommand(), 2) == 0) + { + memcpy(response, f, sizeof(ATCommandResponseFrame)); + success = true; + break; + } + } + + return success; +} + +uint8_t Xbee::sendATCommandInternal(const char* cmd, uint8_t* params, + size_t params_len) +{ + return sendATCommandInternal(getNewFrameID(), cmd, params, params_len); +} + +uint8_t Xbee::sendATCommandInternal(uint8_t tx_frame_id, const char* cmd, + uint8_t* params, size_t params_len) +{ + // Build the AT command + ATCommandFrame at; + at.setATCommand(cmd); + at.setFrameID(tx_frame_id); + if (params_len > 0) + { + if (params_len > MAX_AT_COMMAND_PARAMS_LENGTH) + { + TRACE( + "[Xbee] ERROR! AT Command payload too large. It was " + "truncated.\n"); + params_len = MAX_AT_COMMAND_PARAMS_LENGTH; + } + at.setParameterSize(params_len); + memcpy(at.getCommandDataPointer(), params, params_len); + } + + at.calcChecksum(); + + // Send it + writeFrame(at); + + return tx_frame_id; +} + +void Xbee::handleFrame(APIFrame& frame) +{ + // Set the timestamp to the frame + frame.timestamp = miosix::getTick(); + + switch (frame.frame_type) + { + case FTYPE_RX_PACKET_FRAME: + { + RXPacketFrame* pf = frame.toFrameType<RXPacketFrame>(); + { + Lock<FastMutex> l(mutex_rx_frames); + + if (rx_frames_buf.isFull()) + { + ++status.rx_dropped_buffers; + } + rx_frames_buf.put(*pf); + if (rx_frames_buf.count() > status.frame_buf_max_length) + { + status.frame_buf_max_length = rx_frames_buf.count(); + } + } + wakeReceiver(); + break; + } + case FTYPE_MODEM_STATUS: + { + ModemStatusFrame* ms = frame.toFrameType<ModemStatusFrame>(); + + switch (ms->getStatus()) + { + case MS_HARDWARE_RESET: + TRACE("[Xbee] Modem status: Hardware reset\n"); + break; + case MS_WATCHDOG_TIMER_RESET: + TRACE("[Xbee] Modem status: Watchdog timer reset\n"); + break; + } + break; + } + case FTYPE_TX_STATUS: + { + TXStatusFrame* ts = frame.toFrameType<TXStatusFrame>(); + status.last_tx_status = ts->getDeliveryStatus(); + if (status.last_tx_status != DELS_SUCCESS) + { + status.last_tx_status_error = status.last_tx_status; + } + switch (status.last_tx_status) + { + case DELS_SUCCESS: + break; + default: + TRACE( + "TX Status Error: %d (retries: %d, RD: %s)\n", + status.last_tx_status, ts->getTransmitRetryCount(), + ts->getDiscoveryStatus() == 2 ? "Enabled" : "Disabled"); + break; + } + break; + } + default: + break; + } + + // Pass the frame to the listener + if (frame_listener) + { + frame_listener(frame); + } +} + +void Xbee::setOnFrameReceivedListener(OnFrameReceivedListener listener) +{ + frame_listener = listener; +} + +uint8_t Xbee::getNewFrameID() +{ + uint8_t tx_frame_id = frame_id_counter++; + + // Any value != 0 implies that we DO want a tx status response + if (frame_id_counter == 0) + frame_id_counter = 1; + + return tx_frame_id; +} + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/Xbee.h b/src/shared/drivers/Xbee/Xbee.h index cea5d667c63934b656b2da8f9647a8c34bb1c593..8b5cfb0e2d9b7ae36cf2a09b4f97a141ad288dc5 100644 --- a/src/shared/drivers/Xbee/Xbee.h +++ b/src/shared/drivers/Xbee/Xbee.h @@ -1,5 +1,6 @@ -/* Copyright (c) 2015-2019 Skyward Experimental Rocketry - * Authors: Andrea Milluzzo, Artem Glukhov, Alvise de Faveri Tron, Luca Erbetta +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,628 +23,259 @@ #pragma once -#include <Common.h> -#include <drivers/Transceiver.h> -#include <drivers/spi/SPIDriver.h> #include <miosix.h> -#include <algorithm> -#include <vector> +#include <functional> -#include "ActiveObject.h" #include "XbeeStatus.h" -#include "diagnostic/StackLogger.h" - -using miosix::FastInterruptDisableLock; -using miosix::FastInterruptEnableLock; -using miosix::Thread; +#include "drivers/Transceiver.h" +#include "drivers/Xbee/APIFrameParser.h" +#include "drivers/spi/SPIDriver.h" +#include "utils/collections/CircularBuffer.h" -using miosix::ConditionVariable; using miosix::FastMutex; -using miosix::Lock; -using miosix::Unlock; -using std::vector; +#ifndef USE_MOCK_PERIPHERALS +using GpioType = GpioPin; +#else +#include "utils/testutils/MockGpioPin.h" +using GpioType = MockGpioPin; +#endif namespace Xbee { -// Constants +using ParseResult = APIFrameParser::ParseResult; -// How often should we check if we received a transmit status response. -static constexpr unsigned int SEND_STATUS_POLL_INTERVAL = 10; // ms +static constexpr unsigned int FRAME_POLL_INTERVAL = 10; // ms +static constexpr unsigned int DEFAULT_TX_TIMEOUT = 5000; // ms -static constexpr uint16_t MAX_PAYLOAD_LEN = 0xFFFF; +// Size (in frames) of the receive circular buffer +static constexpr unsigned int RX_FRAMES_BUF_SIZE = 3; -static constexpr uint8_t START_DELIMITER = 0x7E; -static constexpr uint64_t BROADCAST_ADDR = 0xFFFF; -static constexpr uint8_t ID_ACK_DISABLE = 0x00; -static constexpr uint8_t MAX_BROADCAST_HOPS = 0x00; -static constexpr uint8_t TX_STATUS_DELIVERY_SUCCESS = 0x00; +class Xbee : public Transceiver +{ +public: + using OnFrameReceivedListener = std::function<void(APIFrame& frame)>; -// NOTE: 0x43 = Point-Multipoint, No Ack, No route discovery. See transmit -// request frame bit options -static constexpr uint8_t TRANSMIT_OPTIONS = 0x43; + /** + * @brief Constructs a new instance of the Xbee driver. + * + * @param bus The SPI bus where the xbee is connected + * @param cs Xbee SPI Chip Select Pin + * @param attn Xbee ATTN Pin + * @param rst Xbee RST PIN + * @param tx_timeout How long to wait to for a TX Status + */ + Xbee(SPIBusInterface& bus, GpioType cs, GpioType attn, GpioType rst, + long long tx_timeout = DEFAULT_TX_TIMEOUT); -// Frame Sizes -static constexpr uint8_t API_HEADER_SIZE = 3; + /** + * @brief Constructs a new instance of the Xbee driver. + * + * @param bus The SPI bus where the xbee is connected + * @param config Custom SPI bus configuration + * @param cs Xbee SPI Chip Select Pin + * @param attn Xbee ATTN Pin + * @param rst Xbee RST PIN + * @param tx_timeout How long to wait to for a TX Status + * + */ + Xbee(SPIBusInterface& bus, SPIBusConfig config, GpioType cs, GpioType attn, + GpioType rst, long long tx_timeout = DEFAULT_TX_TIMEOUT); -static constexpr uint8_t TX_FRAME_HEADER_SIZE = 14; -static constexpr uint8_t RX_FRAME_HEADER_SIZE = 12; -static constexpr uint8_t CHECKSUM_SIZE = 1; -static constexpr uint8_t TX_STATUS_FRAME_SIZE = 7; -static constexpr uint8_t MODEM_STATUS_FRAME_SIZE = 2; + ~Xbee(); -// Bit definitions for status frame -static constexpr uint8_t BIT_STATUS_RETRY_COUNT = 4; -static constexpr uint8_t BIT_STATUS_DELIVERY = 5; -static constexpr uint8_t BIT_STATUS_DISCOVERY = 6; + /** + * @brief Sends a packet. + * The function blocks until the packet is sent to the peripheral, but does + * not wait for an ACK or send confirmation from the Xbee. Thus, it always + * returns true. + * + * @param pkt Pointer to the packet (needs to be at least pkt_len + * bytes). + * @param pkt_len Lenght of the packet to be sent. + * @return Always true + */ + bool send(uint8_t* pkt, size_t pkt_len) override; -// Waiting thread to be woken when something has been received or must be sent. -// Defined extern so it can be accessed from multiple translation units -extern miosix::Thread* waiting; + /** + * @brief Waits until a new packet is received. + * + * @param buf Buffer to store the received packet into. + * @param buf_max_size Maximum length of the received data. + * @return Size of the data received or -1 if failure + */ + ssize_t receive(uint8_t* buf, size_t buf_max_size) override; -/** - * @brief Handler for the ATTN interrupt: wakes up the thread. - * The ATTN (ATTention Needed) pin is raised when the transceiver has - * received something. - * Call this function from inside an IRQHandler, after you have - * enabled the corresponding interrupt in the NVIC vector. - */ -static void __attribute__((used)) handleATTNInterrupt() -{ - if (waiting) - { - // Wake - waiting->IRQwakeup(); + /** + * @brief Hardware resets the Xbee. + */ + void reset(); - if (waiting->IRQgetPriority() > - miosix::Thread::IRQgetCurrentThread()->IRQgetPriority()) - { - miosix::Scheduler::IRQfindNextThread(); - } + /** + * @brief Signals the receive() function that there is new data available. + * Call this from the ATTN pin interrupt servirce routine, and nowhere else + * plese. + * + */ + void handleATTNInterrupt(); - waiting = nullptr; - } -} + /** + * @brief Wakes the receive function without needing an interrupt + * + * @param force_return Wether receive(..) should return even if it has not + * received any data + */ + void wakeReceiver(bool force_return = false); -/** - * @warning: An IRQ linked with the ATTN pin of the Xbee module must be enabled - * before using this class. See test/misc/xbee-bitrate for an example. - * @warning: Due to the way this driver is written, the SPI bus must be reserved - * only for the XBee device. No other devices can communicate on the same bus. - */ -class Xbee : public Transceiver, public ActiveObject -{ -public: - Xbee(SPIBusInterface& bus, GpioPin cs, GpioPin attn, GpioPin rst, - unsigned int send_timeout = 1000) - : send_timeout(send_timeout), spi_xbee(bus, cs, {}), attn(attn), rst(rst) - { - spi_xbee.config.clock_div = SPIClockDivider::DIV128; - - // No need to configure before each transaction, we are the only device - // on the bus. - spi_xbee.bus.configure(spi_xbee.config); - - reset(); - miosix::Thread::sleep(10); - - spi_xbee.cs.low(); - miosix::Thread::sleep(1); - spi_xbee.cs.high(); - miosix::Thread::sleep(1); - } - - Xbee(SPIBusInterface& bus, GpioPin cs, GpioPin attn, GpioPin rst, - SPIBusConfig spi_config, unsigned int send_timeout = 1000) - : send_timeout(send_timeout), spi_xbee(bus, cs, spi_config), attn(attn), - rst(rst) - { - // No need to configure before each transaction, we are the only device - // on the bus. - spi_xbee.bus.configure(spi_xbee.config); - - reset(); - miosix::Thread::sleep(10); - - spi_xbee.cs.low(); - miosix::Thread::sleep(1); - spi_xbee.cs.high(); - miosix::Thread::sleep(1); - } - - /* - * Send a message through the XBee - * Blocks until the message is sent (successfully or not) + /** + * @brief Sends an AT Command to the Xbee (see datasheet) without waiting + * for a response * - * @param msg Message to be sent - * @param pkt_len Lenght of the message - * @return true The message was sent correctly. - * @return false There was an error sending the message. + * @param cmd Two character string with the name of the command (eg "DB") + * @param params Optional command parameters + * @param params_len Length in bytes of the params array */ - bool send(uint8_t* msg, size_t msg_len) override - { - if (msg == nullptr || msg_len == 0) - { - return false; - } - - // Create a TX API packet - vector<uint8_t> tx_pkt; - buildTxPacket(tx_pkt, msg, msg_len); - - // Send the packet - setTxBuf(tx_pkt.data(), tx_pkt.size()); - - // Wake the runner thread in order to send the data - wakeThread(); - - // Wait until the XBee sends the message or a timeout expires - // It would be cool to have condition variables with timeout to avoid - // polling... - unsigned int timeout = 0; - while (!received_tx_status) - { - if (timeout > send_timeout) - { - // Timeout. Return error - TRACE("[Xbee] Send Timeout!\n"); - ++status.tx_timeout_count; - return false; - } - Thread::sleep(SEND_STATUS_POLL_INTERVAL); - timeout += SEND_STATUS_POLL_INTERVAL; - } - - received_tx_status = false; - if (status.tx_delivery_status != TX_STATUS_DELIVERY_SUCCESS) - { - TRACE("[Xbee] Error: %02X\n", status.tx_delivery_status); - } - return status.tx_delivery_status == TX_STATUS_DELIVERY_SUCCESS; - } - - /* - * Wait for a new message to be received by the XBee - * @param rcv_buf Buffer to store the received message - * @param rcv_buf_len Maximum length of the message to be received - * @return true A message was successfully received - * @return false Received invalid message + void sendATCommand(const char* cmd, uint8_t* params = nullptr, + size_t params_len = 0); + + /** + * @brief Sends an AT Command to the Xbee and wait for a response (see + * datasheet) + * + * @param cmd Two character string with the name of the command (eg "DB") + * @param params Optional command parameters + * @param params_len Length in bytes of the params array + * @param response Where to store the response + * @param timeout Maximum time to wait for the response + * @return true if response received before the timeout, false otherwise */ - ssize_t receive(uint8_t* rcv_buf, size_t rcv_buf_len) override - { - Lock<FastMutex> l(rx_mutex); - - while (rx_frame.size() == 0) - { - rx_cond.wait(l); - } - - if (rx_frame.size() > rcv_buf_len) - { - return -1; - } - else - { - memcpy(rcv_buf, rx_frame.data(), rx_frame.size()); - rcv_buf_len = rx_frame.size(); - - rx_frame.clear(); - - return rcv_buf_len; - } - } - - void stop() override - { - should_stop = true; - wakeThread(); - ActiveObject::stop(); - } - - XbeeStatus getStatus() { return status; } - -protected: + bool sendATCommand(const char* cmd, ATCommandResponseFrame* response, + uint8_t* params = nullptr, size_t params_len = 0, + unsigned int timeout = 1000); + /** - * Sends and receives data, puts the thread to sleep if nothing to - * send/receive. + * @brief Set the frame received listener, called each time a new APIFrame + * is received from the device + * + * @param listener The listener */ - void run() override - { - while (!shouldStop()) - { - // Wait for RX or TX request - { - miosix::FastInterruptDisableLock dLock; - - // Check if we have data to send (tx_buf.size > 0) or receive - // (attn == 0) with disabled interrupts to avoid race conditions - if (attn.value() != 0 && tx_buf.size() == 0) - { - // If we have nothing to receive or send, wait. - waiting = miosix::Thread::getCurrentThread(); - - while (waiting != 0) - { - waiting->IRQwait(); - { - miosix::FastInterruptEnableLock eLock(dLock); - miosix::Thread::yield(); - } - } - } - - StackLogger::getInstance()->updateStack(THID_XBEE); - } - // Transfer any data on the tx buffer and receive any incoming data - transferData(); - } - } + void setOnFrameReceivedListener(OnFrameReceivedListener listener); + + XbeeStatus getStatus(); private: - enum class ParseResult : uint8_t - { - IDLE, - PARSING, - SUCCESS, - FAIL - }; - - enum class ParserState - { - FIND_START, - READ_LENGTH_1, - READ_LENGTH_2, - READ_FRAME, - READ_CHECKSUM - }; - - enum FrameType : uint8_t - { - FRAMETYPE_AT_COMMAND = 0x08, - FRAMETYPE_AT_QUEUE_PARAM_VALUE = 0x09, - FRAMETYPE_TX_REQUEST = 0x10, - FRAMETYPE_EXPICIT_TX_REQUEST = 0x11, - FRAMETYPE_REMOTE_AT_REQUEST = 0x17, - FRAMETYPE_AT_COMMAND_RESPONSE = 0x88, - FRAMETYPE_MODEM_STATUS = 0x8A, - FRAMETYPE_TRANSMIT_STATUS = 0x8B, - FRAMETYPE_ROUTE_INFO_PACKET = 0x8D, - FRAMETYPE_AGGREGATE_ADRESSING_UPDATE = 0x8E, - FRAMETYPE_RX_PACKET = 0x90, - FRAMETYPE_EXPLICIT_RX_PACKET = 0x91, - FRAMETYPE_DATA_SAMPLE_RX_INDICATOR = 0x92, - FRAMETYPE_NODE_IDENTIFICATION_INDICATOR = 0x95, - FRAMETYPE_REMOTE_COMMAND_RESPONSE = 0x97 - }; - - void reset() - { - rst.mode(miosix::Mode::OPEN_DRAIN); - rst.low(); - miosix::delayUs(500); - rst.high(); - } + /** + * @brief Handles an API Frame + * @attention mutex_xbee_comm must be locked before calling this function. + */ + void handleFrame(APIFrame& frame); /** - * Wake the AO thread from another thread. - * Cannot be called with disabled interrupts. + * @brief Sends an AT command to the devices + * @attention mutex_xbee_comm must be locked before calling this function. */ - void wakeThread() - { - FastInterruptDisableLock dLock; + uint8_t sendATCommandInternal(const char* cmd, uint8_t* params = nullptr, + size_t params_len = 0); - if (waiting) - { - waiting->IRQwakeup(); - waiting = nullptr; - } - } + /** + * @brief Sends an AT command to the devices + * @attention mutex_xbee_comm must be locked before calling this function. + */ + uint8_t sendATCommandInternal(uint8_t frame_id, const char* cmd, + uint8_t* params = nullptr, + size_t params_len = 0); + + uint8_t buildTXRequestFrame(TXRequestFrame& tx_req, uint8_t* pkt, + size_t pkt_len); /** - * Transfer data to/from the Xbee. - * Performs a full duplex transaction. + * @brief Fills the provided buffer with data contained in rx_frames_buf */ - void transferData() - { - SPIBusInterface& bus = spi_xbee.bus; - - ParseResult result = ParseResult::IDLE; - - bus.select(spi_xbee.cs); - - vector<uint8_t> data; - - { - // Make a local copy of the TX buffer - FastInterruptDisableLock dLock; - data = tx_buf; - - // Clear the tx buffer - tx_buf.clear(); - } - - // If there is data to send - if (data.size() > 0) - { - // Full duplex transfer, the data vector is replaced with received - // data, if any. - bus.transfer(data.data(), data.size()); - - // Parse the received data - for (uint8_t rx : data) - { - result = parse(rx); - - // We received something - if (result == ParseResult::SUCCESS) - { - handleFrame(parser_buf); - } - } - - // If there's nothing more to parse, return - if (result != ParseResult::PARSING) - { - bus.deselect(spi_xbee.cs); - return; - } - - // If there is more data to be received, continue below. - } - - // Read until we have received a packet (or no packet is found) - do - { - result = parse(bus.read()); - } while (result == ParseResult::PARSING); - - if (result == ParseResult::SUCCESS) - { - handleFrame(parser_buf); - } - else if (result == ParseResult::FAIL) - { - TRACE("[Xbee] Read failed. Parser result: %d\n", (int)result); - } - - bus.deselect(spi_xbee.cs); - } + size_t fillReceiveBuf(uint8_t* buf, size_t buf_max_size); /** - * Parse received data one byte at a time, storing it in the parser_buf. - * When a full packet is received, returns ParseResult::SUCCESS. - * Returns ParseResult::FAIL if a full packet is received but checksum - * verification fails. - * In both cases, the frame is stored in parser_buf. - * - * Returns ParseResult::IDLE if no frame start delimiter has been found yet. - * Returns ParseResult::PARSING if a start delimiter was found and a packet - * is being parsed. + * @brief Reads a single frame from the SPI bus. + * @attention mutex_xbee_comm must be locked before calling this function. + */ + ParseResult readOneFrame(); + + /** + * @brief Reads the SPI bus until an RXPacketFrame is encountered or no more + * data is available + * @attention mutex_xbee_comm must be locked before calling this function. * - * @param byte byte to be parsed - * @return + * @return true RXPacketFrame received + * @return false No more data available on SPI and RXPacketFrame not + * received */ - ParseResult parse(uint8_t byte) - { - switch (parser_state) - { - // Look for the start of frame delimiter - case ParserState::FIND_START: - if (byte == START_DELIMITER) - { - parser_state = ParserState::READ_LENGTH_1; - - // Frame start found, clear old rx buf - parser_buf.clear(); - parser_packet_length = 0; - } - else - { - return ParseResult::IDLE; - } - break; - // Read most significant byte of the length - case ParserState::READ_LENGTH_1: - parser_packet_length = byte << 8; - parser_state = ParserState::READ_LENGTH_2; - break; - // Read least significant byte of the length - case ParserState::READ_LENGTH_2: - parser_packet_length += byte; - - parser_state = ParserState::READ_FRAME; - - // Now that we know how long the packet is, reserve memory to - // store it - parser_buf.reserve(parser_packet_length); - break; - // Read the data frame - case ParserState::READ_FRAME: - parser_buf.push_back(byte); - - if (parser_buf.size() == parser_packet_length) - { - parser_state = ParserState::READ_CHECKSUM; - } - break; - // Read & verify checksum - case ParserState::READ_CHECKSUM: - parser_state = ParserState::FIND_START; - - if (verifyChecksum(parser_buf, byte)) - { - return ParseResult::SUCCESS; - } - else - { - ++status.rx_wrong_checksum; - TRACE("[Xbee] Rx checksum verification failed\n"); - return ParseResult::FAIL; - } - break; - } - - return ParseResult::PARSING; - } - - void handleFrame(const vector<uint8_t>& frame) - { - if (frame.size() == 0) - { - return; - } - // The first byte indicates the frame type - switch (frame[0]) - { - case FRAMETYPE_RX_PACKET: - { - size_t payload_size = frame.size() - RX_FRAME_HEADER_SIZE; - - if (payload_size > 0) - { - { - Lock<FastMutex> l(rx_mutex); - rx_frame.clear(); - rx_frame.insert(rx_frame.end(), - frame.begin() + RX_FRAME_HEADER_SIZE, - frame.end()); - } - rx_cond.signal(); - } - break; - } - case FRAMETYPE_TRANSMIT_STATUS: - { - if (frame.size() == TX_STATUS_FRAME_SIZE) - { - status.tx_retry_count = frame[BIT_STATUS_RETRY_COUNT]; - status.tx_delivery_status = frame[BIT_STATUS_DELIVERY]; - status.tx_discovery_status = frame[BIT_STATUS_DISCOVERY]; - - received_tx_status = true; - } - else - { - TRACE("[Xbee] Wrong TX status frame size.\n"); - } - break; - } - case FRAMETYPE_MODEM_STATUS: - { - if (frame.size() == MODEM_STATUS_FRAME_SIZE) - { - TRACE("[Xbee] Modem status: %d\n", frame[1]); - } - else - { - TRACE("[Xbee] Wrong MODEM status frame size.\n"); - } - break; - } - default: - break; - } - } - - void buildTxPacket(vector<uint8_t>& tx_pkt, uint8_t* msg, size_t msg_len) - { - tx_pkt.reserve(API_HEADER_SIZE + TX_FRAME_HEADER_SIZE + msg_len + - CHECKSUM_SIZE); - - tx_pkt.push_back(START_DELIMITER); - - uint16_t frame_len = msg_len + TX_FRAME_HEADER_SIZE; - // invert pkt_len bytes order - tx_pkt.push_back((frame_len & 0xff00) >> 8); - tx_pkt.push_back(frame_len & 0xff); - - tx_pkt.push_back(FRAMETYPE_TX_REQUEST); - - // Any value != 0 indicates that we want a tx status response - tx_pkt.push_back(0x01); - - // Split the address in bytes & add to the buffer - uint8_t addr_buffer[8]; - memcpy(addr_buffer, &BROADCAST_ADDR, 8); - - for (int i = 7; i >= 0; i--) - { - tx_pkt.push_back(addr_buffer[i]); - } - - // Reserved bytes - tx_pkt.push_back(0xff); - tx_pkt.push_back(0xfe); - - tx_pkt.push_back(MAX_BROADCAST_HOPS); - - tx_pkt.push_back(TRANSMIT_OPTIONS); - - // payload - for (uint32_t i = 0; i < msg_len; i++) - { - tx_pkt.push_back(*(msg + i)); - } - - // Calculate & add checksum - uint32_t checksum = 0; - for (uint32_t i = 3; i < tx_pkt.size(); i++) - { - checksum += tx_pkt.at(i); - } - - tx_pkt.push_back(0xff - (checksum & 0xff)); - } + bool readRXFrame(); /** - * Set the tx buffer with the provided data. + * @brief Writes a frame from the SPI bus. + * @attention mutex_xbee_comm must be locked before calling this function. */ - void setTxBuf(uint8_t* buf, size_t size) - { - // We need to disable interrupts (instead of using a mutex) becouse we - // need to synchronize the access to the tx buf in the waiting thread - // before putting it to sleep (which requires disabling interrupts) - FastInterruptDisableLock dLock; - - // Copy buf into tx_buf, removing old content - tx_buf.clear(); - tx_buf.insert(tx_buf.end(), buf, buf + size); - } - - bool verifyChecksum(vector<uint8_t> frame, uint8_t checksum) - { - // Sum all the bytes including checksum. - // The sum can be stored in a uint8_t since we only care about the least - // significant bits. - uint8_t sum = checksum; - for (size_t i = 0; i < frame.size(); ++i) - { - sum += frame[i]; - } - return sum == 0xFF; - } - - // How long to wait for a transmit status response. - unsigned int send_timeout; - - vector<uint8_t> tx_buf; - - bool received_tx_status = false; - - FastMutex rx_mutex; - vector<uint8_t> rx_frame; - ConditionVariable rx_cond; - - ParserState parser_state = ParserState::FIND_START; - size_t parser_packet_length = 0; - vector<uint8_t> parser_buf; + void writeFrame(APIFrame& frame); - XbeeStatus status; + uint8_t getNewFrameID(); + /** + * @brief Waits until an APIFrame with frame type \p frame_type is received + * on the SPI or until the timeouts expires. The frame is then stored in the + * parsing_api_frame local class varibale. Any frame received by this + * function is automatically passed to the handleFrame() function + * @attention mutex_xbee_comm must be locked before calling this function. + * + * @param poll_interval Polling interval for the attn pin in milliseconds + * @param timeout_tick Tick after which the function should stop waiting for + * frames + * @return true If a frame with type equal to \p frame_type is received + * @return false If the timeout_tick reached + */ + bool waitForFrame(uint8_t frame_type, unsigned int poll_interval, + long long timeout_tick); + + // Synchronizes all communications to the xbee, as they may happen on + // multiple threads + FastMutex mutex_xbee_comm; + + APIFrameParser parser; + // Frame being parsed. Only valid if last call to + // parser.parse() returned ParseResult::SUCCESS + APIFrame parsing_api_frame; + + // Temporary storage for RX packet frames, waiting to be returned by + // receive() + CircularBuffer<RXPacketFrame, RX_FRAMES_BUF_SIZE> rx_frames_buf; + FastMutex mutex_rx_frames; + // RX Packet currently being returned by receive() + RXPacketFrame curr_rx_frame; + // Index of the first byte of the payload of curr_rx_frame that will be + // returned on the next call to receive() + int curr_rx_payload_ptr = -1; + + // SPI defs SPISlave spi_xbee; - GpioPin attn; - GpioPin rst; -}; // namespace Xbee + GpioType attn; + GpioType rst; + + // How long to wait for a TX Status + long long tx_timeout; + + OnFrameReceivedListener frame_listener; + + // Used to generate a unique frame id + uint8_t frame_id_counter = 1; + + // Forces receive() to return even if there is no new data + bool force_rcv_return = false; + + // Status structs + XbeeStatus status; + Stats time_to_send_stats; + + // Waiting thread to be woken when something has been received. + miosix::Thread* receive_thread = 0; +}; } // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/XbeeStatus.h b/src/shared/drivers/Xbee/XbeeStatus.h index a3bfcaa44f8e38d951da2aa1bd1b48b2cb296d71..08cb93c7de37d7eb367de279613e7371a4a7ff47 100644 --- a/src/shared/drivers/Xbee/XbeeStatus.h +++ b/src/shared/drivers/Xbee/XbeeStatus.h @@ -1,17 +1,17 @@ -/* +/* * Copyright (c) 2019 Skyward Experimental Rocketry * Authors: Luca Erbetta - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -25,27 +25,43 @@ #include <cstdint> #include <cstdio> +#include <ostream> +#include <string> +#include "math/Stats.h" namespace Xbee { - struct XbeeStatus +struct XbeeStatus +{ + long long timestamp = 0LL; + + uint8_t last_tx_status_error = 0; + uint8_t last_tx_status = 0; + + StatsResult time_to_send_stats; + + unsigned int tx_timeout_count = 0; + + unsigned int rx_dropped_buffers = 0; + + unsigned int frame_buf_max_length = 0; + + static std::string header() + { + return "timestamp,last_tx_status_error,last_tx_status,tts_stats.min," + "tts_stats.max,tts_stats.mean,tts_stats.stddev,tts_stats.n_" + "samples,tx_timeout_count,rx_dropped_buffers,frame_buf_max_" + "length\n"; + } + + void print(std::ostream& os) const { - uint8_t tx_retry_count = 0; - uint8_t tx_delivery_status = 0; - uint8_t tx_discovery_status = 0; - unsigned int tx_timeout_count = 0; - - unsigned int rx_dropped_buffers = 0; - unsigned int rx_wrong_checksum = 0; - - void print() - { - TRACE("+++XBEE STATUS+++"); - TRACE("TX: Timeouts: %d\n", tx_timeout_count); - TRACE("TXSTATUS: Retries: %d, Delivery: %02X, Discovery: %02X\n", - tx_retry_count, tx_delivery_status, tx_discovery_status); - TRACE("RX: Dropped: %d, Checksum: %d\n", rx_dropped_buffers, rx_wrong_checksum); - - } - }; -} \ No newline at end of file + os << timestamp << "," << (int)last_tx_status_error << "," + << (int)last_tx_status << "," << time_to_send_stats.minValue << "," + << time_to_send_stats.maxValue << "," << time_to_send_stats.mean + << "," << time_to_send_stats.stdev << "," + << time_to_send_stats.nSamples << "," << tx_timeout_count << "," + << rx_dropped_buffers << "," << frame_buf_max_length << "\n"; + } +}; +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/gps/piksi/piksi.cpp b/src/shared/drivers/gps/piksi/piksi.cpp index 8e05730f2c26f78c800ee1e4288d09addae4f198..be56df4ed70c725387a6b279daaae2fb8b4bacad 100644 --- a/src/shared/drivers/gps/piksi/piksi.cpp +++ b/src/shared/drivers/gps/piksi/piksi.cpp @@ -188,8 +188,9 @@ unsigned int Piksi::lookForMessages(uint8_t *buffer, unsigned int size) if (messageSize > size) return consumed; // We don't have the entire message - uint16_t crc = - *reinterpret_cast<uint16_t *>(buffer + messageSize - crcSize); + uint16_t crc; + memcpy(&crc, buffer + messageSize - crcSize, sizeof(crc)); + if (crc16piksi(buffer + 1, messageSize - crcSize - 1) == crc) { processValidMessage(buffer, messageSize); diff --git a/src/shared/drivers/servo/servo.cpp b/src/shared/drivers/servo/servo.cpp index d4222928546a22cfccb6468e248724bea10c1504..d45ba9f2e61e7dc14eb909b3ef7d5ed4e268862d 100644 --- a/src/shared/drivers/servo/servo.cpp +++ b/src/shared/drivers/servo/servo.cpp @@ -26,7 +26,7 @@ #include <cstring> // Initialize the pwm with 50 Hz frequency and 65535 levels of duty cycle -Servo::Servo(PWM::Timer t) : pwm(t, 50, 65535) { memset(&positions, 0, 4); } +Servo::Servo(PWM::Timer t) : pwm(t, 50, 65535) { memset(&positions, 0, 4*sizeof(float)); } Servo::~Servo() {} diff --git a/src/shared/drivers/spi/SPIBus.h b/src/shared/drivers/spi/SPIBus.h index e31c4232974fd91442c5181a45023e5bcfea18d0..c8830353ec802d5d2aa8eb90a50e72ebaedb151c 100644 --- a/src/shared/drivers/spi/SPIBus.h +++ b/src/shared/drivers/spi/SPIBus.h @@ -21,9 +21,19 @@ * THE SOFTWARE. */ +#pragma once + +#include <cassert> + #include "SPIBusInterface.h" -#pragma once + +#ifndef USE_MOCK_PERIPHERALS +using SPIType = SPI_TypeDef; +#else +#include "test/FakeSpiTypedef.h" +using SPIType = FakeSpiTypedef; +#endif /** * @brief Low level driver for communicating on a SPI Bus, provides @@ -37,7 +47,7 @@ public: * * @param spi Pointer to the SPI peripheral to be used */ - SPIBus(SPI_TypeDef* spi); + SPIBus(SPIType* spi) : spi(spi) {} ~SPIBus() {} // Delete copy/move contructors/operators @@ -87,17 +97,23 @@ public: /** * @brief See SPIBusInterface::select() */ - void select(GpioPin& cs) override; + void select(GpioType& cs) override; /** * @brief See SPIBusInterface::deselect() */ - void deselect(GpioPin& cs) override; + void deselect(GpioType& cs) override; /** - * @brief See SPIBusInterface::configure() + * @brief Obtains ownership of the bus, configuring it with the provided + * config. Since this implementation is not syncronized, if acquire() is + * called on an already locked bus, it will: + * - FAIL in DEBUG mode + * - Do nothing when NOT in DEBUG mode + * + * Use SyncedSPIBus if you need to synchronize access to the bus. */ - void configure(SPIBusConfig config) override; + void acquire(SPIBusConfig config) override; protected: /** @@ -122,7 +138,9 @@ protected: */ void transfer(uint8_t* byte); - SPI_TypeDef* spi; + void configure(SPIBusConfig new_config); + + SPIType* spi; SPIBusConfig config{}; bool config_enabled = true; @@ -171,7 +189,7 @@ inline void SPIBus::transfer(uint8_t* data, size_t size) } } -inline void SPIBus::select(GpioPin& cs) +inline void SPIBus::select(GpioType& cs) { cs.low(); if (config.cs_setup_time_us > 0) @@ -180,7 +198,7 @@ inline void SPIBus::select(GpioPin& cs) } } -inline void SPIBus::deselect(GpioPin& cs) +inline void SPIBus::deselect(GpioType& cs) { if (config.cs_hold_time_us > 0) { @@ -202,7 +220,7 @@ inline void SPIBus::write(uint8_t* byte) ; // Clear the RX buffer by accessing the DR register - spi->DR; + (void)spi->DR; } inline void SPIBus::transfer(uint8_t* byte) @@ -235,4 +253,51 @@ inline void SPIBus::read(uint8_t* byte) // Store the received data in the byte *byte = (uint8_t)spi->DR; +} + +inline void SPIBus::acquire(SPIBusConfig new_config) +{ + // Assert that the bus is not already acquired. + // This bus is not syncronized: fail if someone tries to take ownership when + // the bus is already being used. Use SyncedSPIBus if you need to + // synchronize access to the bus +#ifdef DEBUG + assert(isBusy() == false); +#endif + + SPIBusInterface::acquire(new_config); + + configure(new_config); +} + +inline void SPIBus::configure(SPIBusConfig new_config) +{ + // Reconfigure the bus only if config enabled. Do not reconfigure if already + // in the correct configuration. + if (config_enabled && (!first_config_applied || new_config != config)) + { + first_config_applied = true; + config = new_config; + + // Wait until the peripheral is done before changing configuration + while ((spi->SR & SPI_SR_TXE) == 0) + ; + while ((spi->SR & SPI_SR_BSY) > 0) + ; + + spi->CR1 = 0; + + // Configure CPOL & CPHA bits + spi->CR1 |= static_cast<uint32_t>(config.mode); + + // Configure clock division (BR bits) + spi->CR1 |= static_cast<uint32_t>(config.clock_div); + + // Configure LSBFIRST bit + spi->CR1 |= static_cast<uint32_t>(config.bit_order); + + spi->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM // Use software chip-select + | SPI_CR1_MSTR // Master mode + | SPI_CR1_SPE; // Enable SPI + } } \ No newline at end of file diff --git a/src/shared/drivers/spi/SPIBusInterface.h b/src/shared/drivers/spi/SPIBusInterface.h index 9af7e336a23e77f59faf3d23144f216a65e3d3ea..bb313d16714196c504820285ea6ff1cee2922dc2 100644 --- a/src/shared/drivers/spi/SPIBusInterface.h +++ b/src/shared/drivers/spi/SPIBusInterface.h @@ -31,7 +31,12 @@ using miosix::delayUs; using miosix::GpioPin; -class SPITransaction; +#ifndef USE_MOCK_PERIPHERALS +using GpioType = GpioPin; +#else +#include "utils/testutils/MockGpioPin.h" +using GpioType = MockGpioPin; +#endif /** * @brief SPI Clock divider. @@ -118,11 +123,10 @@ struct SPIBusConfig */ class SPIBusInterface { - friend class SPITransaction; public: SPIBusInterface() {} - ~SPIBusInterface() {} + virtual ~SPIBusInterface() {} // Delete copy/move contructors/operators SPIBusInterface(const SPIBusInterface&) = delete; @@ -184,7 +188,7 @@ public: * * @param cs Chip select pin for the slave */ - virtual void select(GpioPin& cs) = 0; + virtual void select(GpioType& cs) = 0; /** * @brief Deselects the slave @@ -192,18 +196,34 @@ public: * @param cs Chip select pin for the slave * @return */ - virtual void deselect(GpioPin& cs) = 0; + virtual void deselect(GpioType& cs) = 0; /** * @brief Configures the bus with the provided configuration parameters. + * Call this before every transaction, after the call to lock() and before + * select(..) * * @param config Configuration parameters * @return */ - virtual void configure(SPIBusConfig config) = 0; + virtual void acquire(SPIBusConfig config) + { + (void)config; + busy = true; + } + + /** + * @brief Releases ownership of the bus + */ + virtual void release() { busy = false; } + + /** + * @brief Checks wether the bus is currently being used + */ + virtual bool isBusy() { return busy; } private: - bool locked = false; // For use by SPITransaction + bool busy = false; // For use by SPITransaction }; /** @@ -215,10 +235,51 @@ struct SPISlave SPIBusConfig config; ///> How the bus should be configured to communicate ///> with the slave. - GpioPin cs; ///> Chip select pin + GpioType cs; ///> Chip select pin - SPISlave(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config) + SPISlave(SPIBusInterface& bus, GpioType cs, SPIBusConfig config) : bus(bus), config(config), cs(cs) { } }; + +/** + * @brief RAII Interface for SPI bus acquisition + * + */ +class SPIAcquireLock +{ +public: + SPIAcquireLock(SPISlave slave) : SPIAcquireLock(slave.bus, slave.config) {} + + SPIAcquireLock(SPIBusInterface& bus, SPIBusConfig cfg) : bus(bus) + { + bus.acquire(cfg); + } + + ~SPIAcquireLock() { bus.release(); } + +private: + SPIBusInterface& bus; +}; + +/** + * @brief RAII Interface for SPI chip selection + * + */ +class SPISelectLock +{ +public: + SPISelectLock(SPISlave slave) : SPISelectLock(slave.bus, slave.cs) {} + + SPISelectLock(SPIBusInterface& bus, GpioType cs) : bus(bus), cs(cs) + { + bus.select(cs); + } + + ~SPISelectLock() { bus.deselect(cs); } + +private: + SPIBusInterface& bus; + GpioType& cs; +}; \ No newline at end of file diff --git a/src/shared/drivers/spi/SPITransaction.cpp b/src/shared/drivers/spi/SPITransaction.cpp index de43334f0cb5770f0fdc6537748929f491e8f27f..b0bc6dfc8d44a4ca0bf93f854b6175d5b1d23c91 100644 --- a/src/shared/drivers/spi/SPITransaction.cpp +++ b/src/shared/drivers/spi/SPITransaction.cpp @@ -23,29 +23,19 @@ #include "SPITransaction.h" -#include <cassert> - SPITransaction::SPITransaction(SPISlave slave) : SPITransaction(slave.bus, slave.cs, slave.config) { } -SPITransaction::SPITransaction(SPIBusInterface& bus, GpioPin cs, +SPITransaction::SPITransaction(SPIBusInterface& bus, GpioType cs, SPIBusConfig config) : bus(bus), cs(cs) { - // Only one SPITransaction may be active at any given time. - // Do not store an instance of SPITransaction for a long time! Create one, - // use it, and destroy it as soon as you are done operating on the bus! - // (just like mutexes) -#ifdef DEBUG - assert(bus.locked == false); -#endif - bus.locked = true; - bus.configure(config); + bus.acquire(config); } -SPITransaction::~SPITransaction() { bus.locked = false; } +SPITransaction::~SPITransaction() { bus.release(); } void SPITransaction::write(uint8_t cmd) { diff --git a/src/shared/drivers/spi/SPITransaction.h b/src/shared/drivers/spi/SPITransaction.h index b75c20d4ab86f2fdc8e57f8f08e6ba6a7e6ee038..3ede19959f6f81d01c2fc5b170512df93cbef8b0 100644 --- a/src/shared/drivers/spi/SPITransaction.h +++ b/src/shared/drivers/spi/SPITransaction.h @@ -71,7 +71,7 @@ public: * @param cs Chip select of the slave to communicate to * @param config Configuration of the bus for the selected slave */ - SPITransaction(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config); + SPITransaction(SPIBusInterface& bus, GpioType cs, SPIBusConfig config); ~SPITransaction(); @@ -161,5 +161,5 @@ public: private: SPIBusInterface& bus; - GpioPin cs; + GpioType cs; }; \ No newline at end of file diff --git a/src/shared/drivers/spi/SPIBus.cpp b/src/shared/drivers/spi/SyncedSPIBus.h similarity index 53% rename from src/shared/drivers/spi/SPIBus.cpp rename to src/shared/drivers/spi/SyncedSPIBus.h index a28324ce1933f44fa699e82658252cae5030ac6d..e71f77b140dcdb67efb2024d46e99c9e117699f8 100644 --- a/src/shared/drivers/spi/SPIBus.cpp +++ b/src/shared/drivers/spi/SyncedSPIBus.h @@ -21,38 +21,31 @@ * THE SOFTWARE. */ +#pragma once + #include "SPIBus.h" +#include <miosix.h> -SPIBus::SPIBus(SPI_TypeDef* spi) : spi(spi) {} +using miosix::FastMutex; -void SPIBus::configure(SPIBusConfig new_config) +/** + * @brief Extension of SPIBus to sync access to the bus between multiple threads + */ +class SyncedSPIBus : public SPIBus { - // Reconfigure the bus only if config enabled. Do not reconfigure if already - // in the correct configuration. - if (config_enabled && (!first_config_applied || new_config != config)) + virtual void acquire(SPIBusConfig config) override { - first_config_applied = true; - config = new_config; - - // Wait until the peripheral is done before changing configuration - while (!(spi->SR & SPI_SR_TXE)) - ; - while ((spi->SR & SPI_SR_BSY)) - ; - - spi->CR1 = 0; + mutex.lock(); + SPIBusInterface::acquire(config); - // Configure CPOL & CPHA bits - spi->CR1 |= static_cast<uint32_t>(config.mode); - - // Configure clock division (BR bits) - spi->CR1 |= static_cast<uint32_t>(config.clock_div); - - // Configure LSBFIRST bit - spi->CR1 |= static_cast<uint32_t>(config.bit_order); + configure(config); + } - spi->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM // Use software chip-select - | SPI_CR1_MSTR // Master mode - | SPI_CR1_SPE; // Enable SPI + virtual void release() override + { + SPIBusInterface::release(); + mutex.unlock(); } -} \ No newline at end of file +private: + FastMutex mutex; +}; \ No newline at end of file diff --git a/src/tests/catch/spidriver/FakeSpiTypedef.h b/src/shared/drivers/spi/test/FakeSpiTypedef.h similarity index 89% rename from src/tests/catch/spidriver/FakeSpiTypedef.h rename to src/shared/drivers/spi/test/FakeSpiTypedef.h index dab7a26fa77879e52f1316d44550f0acac447f2c..433a69f8a46105df511e42cd499dff0364734338 100644 --- a/src/tests/catch/spidriver/FakeSpiTypedef.h +++ b/src/shared/drivers/spi/test/FakeSpiTypedef.h @@ -26,24 +26,10 @@ #include <cstdint> #include <vector> +#include "utils/testutils/MockGpioPin.h" using std::vector; -class FakeGpioPin : public miosix::GpioPin -{ -public: - FakeGpioPin() : GpioPin(GPIOA_BASE,1) {} - - void high() { val = 1; } - - void low() { val = 0; } - - int value() { return val; } - -private: - int val = 1; -}; - /** * @brief Mock STM32F4 SPI peripheral: intercepts register value changes to * emulate a real SPI peripheral / slave. @@ -93,7 +79,7 @@ struct FakeSpiTypedef uint32_t CR2_expected = 0; RegDR DR; - FakeGpioPin cs; + MockGpioPin cs; - FakeSpiTypedef() : DR(*this) {} + FakeSpiTypedef() : DR(*this) {cs.high();} }; \ No newline at end of file diff --git a/src/shared/drivers/spi/MockSPIBus.h b/src/shared/drivers/spi/test/MockSPIBus.h similarity index 52% rename from src/shared/drivers/spi/MockSPIBus.h rename to src/shared/drivers/spi/test/MockSPIBus.h index d3436ffe53216251065910730c580914af6e49f8..8bb7aee7ce76d310667ab36098d8f57dc789c3df 100644 --- a/src/shared/drivers/spi/MockSPIBus.h +++ b/src/shared/drivers/spi/test/MockSPIBus.h @@ -24,9 +24,10 @@ #pragma once #include <cstdint> +#include <cstdio> #include <vector> -#include "SPIBusInterface.h" +#include "../SPIBusInterface.h" using std::vector; @@ -47,10 +48,23 @@ using std::vector; * 5. ??? * 6. Profit. */ + +#ifndef USE_MOCK_PERIPHERALS +#error \ + "SpiBusInterface must be built using MockGpioPin (-DUSE_MOCK_PERIPHERALS)" +#endif + +#include "utils/testutils/MockGpioPin.h" + +using miosix::FastMutex; +using miosix::Lock; class MockSPIBus : public SPIBusInterface { public: - MockSPIBus() {} + MockSPIBus(SPIBusConfig expected_config) : expected_config(expected_config) + { + } + ~MockSPIBus() {} // Delete copy/move contructors/operators @@ -63,17 +77,17 @@ public: /** * @brief See SPIBusInterface::write() */ - void write(uint8_t byte) override; + virtual void write(uint8_t byte) override; /** * @brief See SPIBusInterface::write() */ - void write(uint8_t* data, size_t size) override; + virtual void write(uint8_t* data, size_t size) override; /** * @brief See SPIBusInterface::read() */ - uint8_t read() override; + virtual uint8_t read() override; /** * @brief See SPIBusInterface::read() @@ -83,57 +97,100 @@ public: /** * @brief See SPIBusInterface::transfer() */ - uint8_t transfer(uint8_t data) override; + virtual uint8_t transfer(uint8_t data) override; /** * @brief See SPIBusInterface::transfer() */ - void transfer(uint8_t* data, size_t size) override; + virtual void transfer(uint8_t* data, size_t size) override; /** * @brief See SPIBusInterface::select() * - * @param cs Not used, pass any GpioPin */ - void select(GpioPin& cs) override; + virtual void select(GpioType& cs) override; /** * @brief See SPIBusInterface::deselect() * - * @param cs Not used, pass any GpioPin */ - void deselect(GpioPin& cs) override; + virtual void deselect(GpioType& cs) override; /** - * @brief See SPIBusInterface::configure() + * @brief See SPIBusInterface::acquire() */ - void configure(SPIBusConfig config) override; + virtual void acquire(SPIBusConfig config) override; /** * @brief Wether the chip select is asserted or not */ - bool isSelected() { return selected; } + virtual bool isSelected() + { + + Lock<FastMutex> l(mutex); + return selected; + } + + virtual vector<uint8_t> getOutBuf() + { + Lock<FastMutex> l(mutex); + return out_buf; + } + + virtual vector<uint8_t> getInBuf() + { + Lock<FastMutex> l(mutex); + return in_buf; + } + + virtual void push(uint8_t* data, size_t len); + + virtual void clearBuffers() + { + out_buf.clear(); + in_buf.clear(); + in_buf_pos_cntr = 0; + } + +protected: + FastMutex mutex; vector<uint8_t> out_buf; // Data written on the bus are stored here vector<uint8_t> in_buf; // Store here data to be read from the bus - - unsigned int in_buf_pos = 0; // Read data iterator + size_t in_buf_pos_cntr = 0; SPIBusConfig expected_config; // Expected configuration of the bus -private: + virtual uint8_t _read(); + virtual void _write(uint8_t); + + virtual void _push(uint8_t* data, size_t len); + bool canCommunicate(); SPIBusConfig current_config; bool selected = false; }; -bool MockSPIBus::canCommunicate() +inline bool MockSPIBus::canCommunicate() { - return selected && current_config == expected_config; + bool result = selected && current_config == expected_config; + if (!result) + { + printf("Error, cannot communicato on MockSPIBus: "); + if (!selected) + { + printf("Chip select not asserted\n"); + } + else + { + printf("Incorrect configuration\n"); + } + } + return result; } -void MockSPIBus::write(uint8_t byte) +inline void MockSPIBus::_write(uint8_t byte) { if (canCommunicate()) { @@ -145,95 +202,97 @@ void MockSPIBus::write(uint8_t byte) } } -void MockSPIBus::write(uint8_t* data, size_t size) +inline void MockSPIBus::write(uint8_t byte) { - if (canCommunicate()) - { - out_buf.insert(out_buf.end(), data, data + size); - } - else - { - out_buf.insert(out_buf.end(), size, 0); - } + Lock<FastMutex> l(mutex); + _write(byte); } -uint8_t MockSPIBus::read() +inline void MockSPIBus::write(uint8_t* data, size_t size) { - if (canCommunicate()) + Lock<FastMutex> l(mutex); + + for (size_t i = 0; i < size; i++) { - return in_buf[in_buf_pos++]; + _write(data[i]); } - return 0; } -void MockSPIBus::read(uint8_t* data, size_t size) +inline uint8_t MockSPIBus::read() { - if (canCommunicate()) - { - for (size_t i = 0; i < size; i++) - { - *data = in_buf[in_buf_pos++]; - data++; - } - } - else - { - for (size_t i = 0; i < size; i++) - { - *data = 0; - data++; - } - } + Lock<FastMutex> l(mutex); + return _read(); } -uint8_t MockSPIBus::transfer(uint8_t data) +inline void MockSPIBus::read(uint8_t* data, size_t size) { - if (canCommunicate()) + Lock<FastMutex> l(mutex); + for (size_t i = 0; i < size; i++) { - - out_buf.push_back(data); - return in_buf[in_buf_pos++]; - } - else - { - - out_buf.push_back(0); - return 0; + *data = _read(); + data++; } } -void MockSPIBus::transfer(uint8_t* data, size_t size) +inline uint8_t MockSPIBus::_read() { if (canCommunicate()) { - for (size_t i = 0; i < size; i++) + if (in_buf_pos_cntr < in_buf.size()) { - out_buf.push_back(*data); - *data = in_buf[in_buf_pos++]; - data++; + return in_buf[in_buf_pos_cntr++]; } } - else + return 0; +} + +inline uint8_t MockSPIBus::transfer(uint8_t data) +{ + Lock<FastMutex> l(mutex); + _write(data); + return _read(); +} + +inline void MockSPIBus::transfer(uint8_t* data, size_t size) +{ + Lock<FastMutex> l(mutex); + for (size_t i = 0; i < size; i++) { - for (size_t i = 0; i < size; i++) - { - out_buf.push_back(0); - *data = 0; - data++; - } + _write(data[i]); + data[i] = _read(); } } -void MockSPIBus::select(GpioPin& cs) +inline void MockSPIBus::select(GpioType& cs) { - (void)cs; + Lock<FastMutex> l(mutex); + cs.low(); selected = true; } -void MockSPIBus::deselect(GpioPin& cs) +inline void MockSPIBus::deselect(GpioType& cs) { - (void)cs; + Lock<FastMutex> l(mutex); + cs.high(); selected = false; } -void MockSPIBus::configure(SPIBusConfig config) { current_config = config; } \ No newline at end of file +inline void MockSPIBus::acquire(SPIBusConfig config) +{ + Lock<FastMutex> l(mutex); + + SPIBusInterface::acquire(config); + + current_config = config; +} + +inline void MockSPIBus::push(uint8_t* data, size_t len) +{ + Lock<FastMutex> l(mutex); + _push(data, len); +} + +inline void MockSPIBus::_push(uint8_t* data, size_t len) +{ + in_buf.insert(in_buf.end(), data, data + len); +} \ No newline at end of file diff --git a/src/shared/logger/Deserializer.h b/src/shared/logger/Deserializer.h index aa673fdaa38496cd21644179171d1aae112a75e2..7c1193f8620d5b4e2c56d9d5ad00ce39c1aee02c 100644 --- a/src/shared/logger/Deserializer.h +++ b/src/shared/logger/Deserializer.h @@ -83,7 +83,7 @@ public: return false; } - char c_filename[64]; + char c_filename[128]; sprintf(c_filename, "%s%s_%s.csv", prefix.c_str(), logFile.c_str(), typeid(T).name()); diff --git a/src/shared/logger/Logger.cpp b/src/shared/logger/Logger.cpp index afa1d7656c1076a320c80987a856a17df3062dbb..2a4e6e789f387ca4c5b5e5d0f9d8f5e8c8473ed6 100644 --- a/src/shared/logger/Logger.cpp +++ b/src/shared/logger/Logger.cpp @@ -26,16 +26,19 @@ ***************************************************************************/ #include "Logger.h" + +#include <errno.h> #include <fcntl.h> #include <interfaces/atomic_ops.h> #include <sys/stat.h> #include <sys/types.h> #include <tscpp/buffer.h> + #include <stdexcept> + #include "Debug.h" #include "diagnostic/SkywardStack.h" #include "diagnostic/StackLogger.h" -#include <errno.h> using namespace std; using namespace miosix; @@ -74,7 +77,10 @@ int Logger::start() file = fopen(filename.c_str(), "ab"); if (file == NULL) + { + fileNumber = -1; throw runtime_error("Error opening log file"); + } setbuf(file, NULL); // The boring part, start threads one by one and if they fail, undo diff --git a/src/shared/logger/Logger.h b/src/shared/logger/Logger.h index 0794e0fa6f67f2780226b17ec41612edecce2610..1c313805a21ea9d9794bb99f79a17120bb1528bd 100644 --- a/src/shared/logger/Logger.h +++ b/src/shared/logger/Logger.h @@ -28,11 +28,13 @@ #pragma once #include <miosix.h> + #include <cstdio> #include <list> #include <queue> #include <string> #include <type_traits> + #include "LogStats.h" using std::string; @@ -101,9 +103,9 @@ public: } /** - * Returns current log filename - * @return - */ + * Returns current log filename + * @return + */ string getFileName() { return getFileName(fileNumber); } LogStats getLogStats() { return s; } @@ -129,7 +131,10 @@ public: template <typename T> LogResult log(const T &t) { - // static_assert(std::is_trivially_copyable<T>::value,""); + static_assert( + std::is_trivially_copyable<T>::value, + "A type T must be trivially copyable in order to be logged!"); + return logImpl(typeid(t).name(), &t, sizeof(t)); } @@ -176,8 +181,8 @@ private: static const unsigned int filenameMaxRetry = 100; ///< Limit on new filename - static const unsigned int maxRecordSize = 256; ///< Limit on logged data - static const unsigned int numRecords = 1024; ///< Size of record queues + static const unsigned int maxRecordSize = 512; ///< Limit on logged data + static const unsigned int numRecords = 512; ///< Size of record queues static const unsigned int bufferSize = 64 * 1024; ///< Size of each buffer static const unsigned int numBuffers = 8; ///< Number of buffers diff --git a/src/shared/math/Stats.cpp b/src/shared/math/Stats.cpp index 3b3567a716bd53d048fa16aa723c5de331a22729..43231d8f20f998784a19af71c6062ef0b9053be2 100644 --- a/src/shared/math/Stats.cpp +++ b/src/shared/math/Stats.cpp @@ -42,7 +42,7 @@ ostream& operator<<(ostream& os, const StatsResult& sr) Stats::Stats() : minValue(numeric_limits<float>::max()), - maxValue(numeric_limits<float>::min()), mean(0.f), m2(0.f), n(0) + maxValue(numeric_limits<float>::lowest()), mean(0.f), m2(0.f), n(0) { } @@ -64,7 +64,7 @@ void Stats::add(float data) void Stats::reset() { minValue = numeric_limits<float>::max(); - maxValue = numeric_limits<float>::min(); + maxValue = numeric_limits<float>::lowest(); mean = 0.f; m2 = 0.f; n = 0; diff --git a/src/shared/sensors/MS580301BA07/MS580301BA07.h b/src/shared/sensors/MS580301BA07/MS580301BA07.h index efc5e36187084a22c26c4e42e0f70044e9162758..4aefbee2a3241cbce27eb82adc44eaa07f2454bf 100644 --- a/src/shared/sensors/MS580301BA07/MS580301BA07.h +++ b/src/shared/sensors/MS580301BA07/MS580301BA07.h @@ -123,7 +123,7 @@ public: case STATE_INIT: spi.write(CONVERT_D1_4096); mStatus = STATE_SAMPLED_PRESSURE; - + break; case STATE_SAMPLED_PRESSURE: spi.read(ADC_READ, rcvbuf, 3, false); mInternalPressure = rcvbuf[2] | ((uint32_t)rcvbuf[1] << 8) | diff --git a/src/shared/utils/gui/GridLayout.h b/src/shared/utils/gui/GridLayout.h new file mode 100644 index 0000000000000000000000000000000000000000..d5bd5441802295263d742a3044a29fd860f5fc12 --- /dev/null +++ b/src/shared/utils/gui/GridLayout.h @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <mxgui/misc_inst.h> + +#include <map> + +#include "View.h" + +/** + * @brief Displays childs in a num_rows*num_cols grid + */ +class GridLayout : public View +{ +public: + using GridPosition = std::pair<uint8_t, uint8_t>; + + /** + * @brief Creates a new GridLayout + * + * @param num_rows Number of rows + * @param num_cols Number of columns + * @param spacing Distance in pixels between each cell + */ + GridLayout(uint8_t num_rows, uint8_t num_cols, short int spacing = 0) + : View(), num_rows(num_rows), num_cols(num_cols), spacing(spacing) + { + } + + virtual ~GridLayout() {} + + void setCell(View* child, unsigned int position) + { + uint8_t col = position % num_cols; + uint8_t row = (position - col) / num_cols; + + setCell(child, row, col); + } + + void setCell(View* child, uint8_t row, uint8_t col) + { + if (row >= num_rows || col >= num_cols) + { + return; + } + + map_childs[GridPosition(row, col)] = child; + + updateChildBounds(); + invalidate(); + } + + View* getCell(uint8_t row, uint8_t col) + { + GridPosition pos(row, col); + + if (map_childs.count(pos) > 0) + return map_childs[pos]; + else + return nullptr; + } + + View* getCell(unsigned int position) + { + uint8_t col = position % num_cols; + uint8_t row = position - col / num_rows; + + return getCell(row, col); + } + + void clearCell(View* child) + { + for (auto it = map_childs.begin(); it != map_childs.end(); it++) + { + if (it->second == child) + { + map_childs.erase(it); + break; + } + } + + invalidate(); + } + + /** + * @brief Wether to draw the borders of each cell or not + * + * @param draw_border True: draw the border + * @param color Border color + */ + void setDrawBorder(bool draw_border, mxgui::Color color = mxgui::white) + { + this->draw_border = draw_border; + border_color = color; + + invalidate(); + } + + void setBounds(Bounds bounds) override + { + View::setBounds(bounds); + + updateChildBounds(); + } + + virtual void draw(mxgui::DrawingContext& context) override + { + View::draw(context); + + for (uint8_t row = 0; row < num_rows; ++row) + { + for (uint8_t col = 0; col < num_cols; ++col) + { + GridPosition pos(row, col); + + bool child_selected = false; + if (map_childs.count(pos) > 0) + { + map_childs[pos]->draw(context); + child_selected = map_childs[pos]->isSelected(); + } + + if (draw_border && !child_selected) + { + context.drawRectangle( + map_child_bounds[pos].topLeft(), + map_child_bounds[pos].bottomRight(), border_color); + } + } + } + } + + uint8_t getRows() { return num_rows; } + + uint8_t getCols() { return num_cols; } + + std::vector<View*> getChilds() override + { + std::vector<View*> out; + for (auto it = map_childs.begin(); it != map_childs.end(); it++) + { + out.push_back(it->second); + } + return out; + } + +private: + void updateChildBounds() + { + Bounds bounds = getBounds(); + Bounds child_bounds; + + child_bounds.size.width = std::max( + 0, (bounds.size.width - (num_cols + 1) * spacing) / num_cols); + child_bounds.size.height = std::max( + 0, (bounds.size.height - (num_rows + 1) * spacing) / num_rows); + + for (uint8_t row = 0; row < num_rows; ++row) + { + for (uint8_t col = 0; col < num_cols; ++col) + { + GridPosition pos(row, col); + + child_bounds.pos.x = bounds.pos.x + (col + 1) * spacing + + col * child_bounds.size.width; + child_bounds.pos.y = bounds.pos.y + (row + 1) * spacing + + row * child_bounds.size.height; + + map_child_bounds[pos] = child_bounds; + if (map_childs.count(pos) > 0) + { + map_childs[pos]->setBounds(child_bounds); + } + } + } + } + + uint8_t num_rows; + uint8_t num_cols; + short int spacing; + + bool draw_border = false; + mxgui::Color border_color = mxgui::white; + + std::map<GridPosition, View*> map_childs; + std::map<GridPosition, Bounds> map_child_bounds; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/ImageView.h b/src/shared/utils/gui/ImageView.h new file mode 100644 index 0000000000000000000000000000000000000000..9ecdc37bba903f81cec7c0fb2823253f9ea6ae76 --- /dev/null +++ b/src/shared/utils/gui/ImageView.h @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <mxgui/image.h> + +#include "View.h" + +/** + * @brief Simple view that displays an image + */ +class ImageView : public View +{ +public: + ImageView(const mxgui::Image* image) : image(image) {} + + void setImage(mxgui::Image* image) + { + this->image = image; + invalidate(); + } + + const mxgui::Image* getImage() { return image; } + + void draw(mxgui::DrawingContext& dc) override + { + if (isInvalidated()) + { + View::draw(dc); + + dc.clippedDrawImage(getBounds().topLeft(), getBounds().topLeft(), + getBounds().bottomRight(), *image); + } + } +private: + const mxgui::Image* image; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/Misc.h b/src/shared/utils/gui/Misc.h new file mode 100644 index 0000000000000000000000000000000000000000..0576c5afa101d13646e28cf60a0a4f48538f7f74 --- /dev/null +++ b/src/shared/utils/gui/Misc.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once +#include <mxgui/display.h> + +/** + * @brief Converts a 24bit color to a 16bit color + * + * @param rgb 24 bit color + * @return Color Closest 16 bit color + */ +mxgui::Color color24to16(uint32_t rgb) +{ + uint8_t r, g, b; + r = ((rgb >> 2) & 0xFF) >> 3; + g = ((rgb >> 1) & 0xFF) >> 3; + b = (rgb & 0xFF) >> 3; + return ((b << 10) | (g << 5) | (r)); +} \ No newline at end of file diff --git a/src/shared/utils/gui/NavController.h b/src/shared/utils/gui/NavController.h new file mode 100644 index 0000000000000000000000000000000000000000..7cd611aceb485ecebd16b241abc13f8162aa0407 --- /dev/null +++ b/src/shared/utils/gui/NavController.h @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <vector> + +#include "Debug.h" +#include "TextView.h" +#include "View.h" +#include "utils/ButtonHandler.h" + +/** + * @brief UI navigation controller: listens for button clicks and dispatches the + * interactions to the view tree. + */ +class NavController +{ +public: + NavController() {} + + /** + * @brief + * + * @param root + */ + void updateViewTree(View* root) + { + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index)->setSelected(false); + } + + vec_selectable.clear(); + selected_index = 0; + + updateSelectableViews(root); + + if (vec_selectable.size() > 0) + { + vec_selectable.at(0)->setSelected(true); + } + } + + void onButtonPress(ButtonPress press) + { + switch (press) + { + case ButtonPress::SHORT: + if (vec_selectable.size() > 0) + { + selectNext(); + } + break; + case ButtonPress::DOWN: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::BTN_DOWN); + } + break; + case ButtonPress::UP: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::BTN_UP); + } + break; + case ButtonPress::LONG: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::CLICK); + } + break; + case ButtonPress::VERY_LONG: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::LONG_CLICK); + } + break; + default: + break; + } + } + +private: + void selectNext() + { + // Deselect old drawble + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index)->setSelected(false); + } + + if (vec_selectable.size() > 0) + { + selected_index = (selected_index + 1) % vec_selectable.size(); + + vec_selectable.at(selected_index)->setSelected(true); + + TextView* text = + dynamic_cast<TextView*>(vec_selectable.at(selected_index)); + + if (text) + { + TRACE("[NavController] %s\n", text->getText().c_str()); + } + } + } + + void updateSelectableViews(View* root) + { + std::vector<View*> childs = root->getChilds(); + for (auto child : childs) + { + if (child->isSelectable()) + { + vec_selectable.push_back(child); + } + updateSelectableViews(child); + } + } + + unsigned int selected_index = 0; + std::vector<View*> vec_selectable; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/OptionView.h b/src/shared/utils/gui/OptionView.h new file mode 100644 index 0000000000000000000000000000000000000000..9dbef9b77442c23fcfec96bb8d1581aabac8c1df --- /dev/null +++ b/src/shared/utils/gui/OptionView.h @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <map> +#include <string> + +#include "GridLayout.h" +#include "TextView.h" + +/** + * @brief View used to display an option list, so the user can select one by + * clicking on it. + */ +class OptionView : public View +{ +public: + using OnOptionChosenListener = std::function<void(unsigned int id)>; + + OptionView(std::string name, std::map<uint8_t, std::string> options, + uint8_t default_option = 0, uint8_t num_cols = 4) + : tv_title(new TextView(name)) + { + uint8_t num_rows = options.size() / num_cols; + if (options.size() % num_cols != 0) + { + ++num_rows; + } + + grid_options = new GridLayout(num_rows, num_cols); + + unsigned int grid_pos = 0; + unsigned int i = 0; + for (auto it = options.begin(); it != options.end(); it++) + { + using namespace std::placeholders; + + TextView* tv_opt = new TextView(it->second); + tv_opt->setSelectable(true); + tv_opt->addOnInteractionListener( + std::bind(&OptionView::onOptionClick, this, _1, _2)); + tv_opt->setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + map_options[tv_opt] = it->first; + + grid_options->setCell(tv_opt, grid_pos++); + + if (i == default_option) + { + selectOption(tv_opt); + } + + ++i; + } + } + + virtual ~OptionView() + { + delete grid_options; + delete tv_title; + for (auto it = map_options.begin(); it != map_options.end(); it++) + { + delete it->first; + } + } + + void setBounds(Bounds bounds) override + { + View::setBounds(bounds); + + Bounds title_bounds = getBounds(); + title_bounds.size.height = tv_title->getFont().getHeight(); + tv_title->setBounds(title_bounds); + + Bounds grid_bounds = getBounds(); + grid_bounds.pos.y = getBounds().pos.y + title_bounds.size.height + 1; + grid_bounds.size.height = + getBounds().size.height - title_bounds.size.height; + + grid_options->setBounds(grid_bounds); + } + + /** + * @brief Adds a callback to be called each time an option is selected by + * the user + */ + void addOnOptionChosenListener(OnOptionChosenListener listener) + { + opt_listeners.push_back(listener); + } + + std::vector<View*> getChilds() override + { + std::vector<View*> out; + out.push_back(tv_title); + out.push_back(grid_options); + + return out; + } + + void draw(mxgui::DrawingContext& dc) override + { + View::draw(dc); + tv_title->draw(dc); + grid_options->draw(dc); + } + +private: + void selectOption(TextView* opt) + { + if (selected_option != nullptr) + { + selected_option->setBackgroundColor(col_bg_normal); + } + + selected_option = opt; + + selected_option->setBackgroundColor(col_bg_highlight); + } + + void onOptionClick(View* option, Interaction action) + { + if (action == Interaction::CLICK) + { + TextView* textview = static_cast<TextView*>(option); + + selectOption(textview); + + for (auto l : opt_listeners) + { + if (l != nullptr) + { + l(map_options[textview]); + } + } + } + } + + TextView* tv_title; + mxgui::Color col_bg_normal = mxgui::black; + mxgui::Color col_bg_highlight = mxgui::blue; + + GridLayout* grid_options; + std::map<TextView*, uint8_t> map_options; + + std::vector<OnOptionChosenListener> opt_listeners; + + TextView* selected_option = nullptr; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/ScreenManager.h b/src/shared/utils/gui/ScreenManager.h new file mode 100644 index 0000000000000000000000000000000000000000..b8b34e352caadf4908c45dcb782a6b5e1bc7394b --- /dev/null +++ b/src/shared/utils/gui/ScreenManager.h @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <miosix.h> + +#include <map> +#include <deque> + +#include "NavController.h" +#include "View.h" + +/** + * @brief UI Thread: Manages multiple view trees ("Screen") and draws the active + * one at the provided refresh rate + */ +class ScreenManager : public ActiveObject +{ +public: + ScreenManager(mxgui::DisplayManager& display, unsigned int refresh_rate) + : dc(display.getDisplay()), refresh_interval(1000 / refresh_rate) + { + } + + void showScreen(uint8_t id) + { + active_screen = id; + controller.updateViewTree(screens[id]); + } + + uint8_t getScreen() { return active_screen; } + + void addScreen(uint8_t id, View* root) + { + screens[id] = root; + + root->setBounds({{0, 0}, {dc.getWidth(), dc.getHeight()}}); + + if (screens.size() == 1) + { + showScreen(id); + } + } + + void onButtonPress(ButtonPress press) { controller.onButtonPress(press); } + + mxgui::DrawingContext& getDrawingContext() { return dc; } + +protected: + void run() override + { + uint8_t last_screen = 0; + while (!shouldStop()) + { + if (active_screen != last_screen) + { + last_screen = active_screen; + dc.clear(mxgui::black); + screens[active_screen]->invalidateTree(); + } + + long long start = miosix::getTick(); + + drawViewTree(screens[active_screen], dc); + + Thread::sleepUntil(start + refresh_interval); + } + } +private: + /** + * @brief Draws the provided view tree on screen + * + * @param root Root of the view tree + * @param dc Drawing context + */ + void drawViewTree(View* root, mxgui::DrawingContext& dc) + { + // Avoid recursion + std::deque<View*> views_dc; + views_dc.push_back(root); + + while(views_dc.size() != 0) + { + View* view = views_dc.front(); + views_dc.pop_front(); + + view->draw(dc); + + for(View* c : view->getChilds()) + { + views_dc.push_back(c); + } + } + } + + mxgui::DrawingContext dc; + + unsigned int refresh_interval; + + NavController controller; + std::map<uint8_t, View*> screens; + + uint8_t active_screen = 0; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/TextView.h b/src/shared/utils/gui/TextView.h new file mode 100644 index 0000000000000000000000000000000000000000..a695e0ccd9996cea6a17c2533e5810890928c9a9 --- /dev/null +++ b/src/shared/utils/gui/TextView.h @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <mxgui/misc_inst.h> + +#include "View.h" + +/** + * @brief Simple view to display text on screen. + * + */ +class TextView : public View +{ +public: + /** + * @brief Creates a new TextView + * + * @param text Text + * @param text_color Text color + * @param padding Spacing from view bounds in pixels + */ + TextView(std::string text = "", mxgui::Color text_color = mxgui::white, + uint8_t padding = 1) + : View(), text(text), col_text(text_color), padding(padding) + { + } + + void setVerticalAlignment(VertAlignment alignment) + { + vert_align = alignment; + invalidate(); + } + + void setHorizontalAlignment(HorizAlignment alignment) + { + horiz_align = alignment; + invalidate(); + } + + void setAlignment(HorizAlignment horiz_align, VertAlignment vert_align) + { + setVerticalAlignment(vert_align); + setHorizontalAlignment(horiz_align); + } + + void setText(std::string text) + { + if (text != this->text) + { + invalidate(); + this->text = text; + } + } + + std::string getText() { return text; } + + virtual void setTextColor(mxgui::Color color) + { + if (color != col_text) + { + this->col_text = color; + invalidate(); + } + } + + mxgui::Color getTextColor() { return col_text; } + + virtual void draw(mxgui::DrawingContext& dc) override + { + if (isInvalidated()) + { + View::draw(dc); + + Bounds padded_bounds = getBounds(); + padded_bounds.pos += Position{padding, padding}; + padded_bounds.size.height -= padding; + padded_bounds.size.width -= padding; + + Position top_left = padded_bounds.topLeft(); + switch (vert_align) + { + case VertAlignment::BOTTOM: + top_left.y = + padded_bounds.bottomLeft().y - dc.getFont().getHeight(); + break; + case VertAlignment::CENTER: + top_left.y = + padded_bounds.pos.y + + (padded_bounds.size.height - dc.getFont().getHeight()) / + 2; + break; + case VertAlignment::TOP: + break; + } + + switch (horiz_align) + { + case HorizAlignment::RIGHT: + top_left.x = + padded_bounds.topRight().x - getTextWidth(text); + break; + case HorizAlignment::CENTER: + top_left.x = + padded_bounds.pos.x + + (padded_bounds.size.width - getTextWidth(text)) / 2; + break; + case HorizAlignment::LEFT: + break; + } + + mxgui::Font old_font = dc.getFont(); + dc.setFont(font); + + dc.setTextColor(getTextColor(), getBackgroundColor()); + dc.clippedWrite(top_left, padded_bounds.topLeft(), + padded_bounds.bottomRight(), text); + + dc.setFont(old_font); + } + } + + void setFont(mxgui::Font font) + { + this->font = font; + invalidate(); + } + + mxgui::Font getFont() { return font; } + +protected: + short int getTextWidth(std::string text) + { + short int w = 0; + for (char c : text) + { + if (c >= font.getStartChar() && c <= font.getEndChar()) + { + if (font.isFixedWidth()) + { + w += font.getWidth(); + } + else + { + w += font.getWidths()[c - font.getStartChar()]; + } + } + } + return w; + } + +private: + mxgui::Font font = mxgui::tahoma; + VertAlignment vert_align = VertAlignment::TOP; + HorizAlignment horiz_align = HorizAlignment::LEFT; + std::string text; + + mxgui::Color col_text = mxgui::white; + uint8_t padding; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/VerticalLayout.h b/src/shared/utils/gui/VerticalLayout.h new file mode 100644 index 0000000000000000000000000000000000000000..7d19c2eb02f6c44bbdcfed1f594c425feb00f4fa --- /dev/null +++ b/src/shared/utils/gui/VerticalLayout.h @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "View.h" + +/** + * @brief Positions the childs in a vertical grid. The height of each child is + * dictated by its weight parameter + */ +class VerticalLayout : public View +{ +public: + /** + * @brief Creates a new VerticalLayout + * + * @param spacing Vertical space between each child in pixels + */ + VerticalLayout(short int spacing = 0) : View(), spacing(spacing) {} + + /** + * @brief Adds a view to the layout. + * + * @param view Pointer to the view to be added + * @param weight Relative weight of the view to determine its display height + */ + void addView(View* view, float weight) + { + if (weight < 0) + weight = 0; + childs.push_back({weight, view}); + weigth_sum += weight; + + // Recalculated child bounds + updateChildBounds(); + + // Redraw + invalidate(); + } + + void setBounds(Bounds bounds) override + { + View::setBounds(bounds); + + updateChildBounds(); + } + + void draw(mxgui::DrawingContext& dc) override + { + View::draw(dc); + + for (auto view : childs) + { + view.drawable->draw(dc); + } + } + + virtual std::vector<View*> getChilds() override + { + std::vector<View*> out; + out.reserve(childs.size()); + for (auto v : childs) + { + out.push_back(v.drawable); + } + return out; + } + +private: + void updateChildBounds() + { + if (weigth_sum > 0) + { + short int aval_height = + getBounds().size.height - (childs.size() - 1) * spacing; + + short int y = 0; + + for (auto view : childs) + { + Bounds view_bounds{{0, y}, {getBounds().size.width, 0}}; + + view_bounds.size.height = + aval_height / weigth_sum * view.vert_weight; + view_bounds.pos += getBounds().pos; + + view.drawable->setBounds(view_bounds); + + y += view_bounds.size.height + spacing; + } + } + } + + struct Child + { + float vert_weight; + View* drawable; + }; + + float weigth_sum = 0; + short int spacing; + std::vector<Child> childs; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/View.h b/src/shared/utils/gui/View.h new file mode 100644 index 0000000000000000000000000000000000000000..ffa4a844984c942f2e507d77598d75a09d476fb3 --- /dev/null +++ b/src/shared/utils/gui/View.h @@ -0,0 +1,261 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <mxgui/display.h> +#include <mxgui/misc_inst.h> + +#include <functional> + +#include "Misc.h" + +struct Size +{ + short int width; + short int height; +}; + +struct Position +{ + short int x; + short int y; + + operator mxgui::Point() { return mxgui::Point(x, y); } + + Position operator+(const Position& other) + { + return {short(x + other.x), short(y + other.y)}; + } + + Position operator-(const Position& other) + { + return {short(x - other.x), short(y - other.y)}; + } + + void operator+=(const Position& other) + { + x += other.x; + y += other.y; + } + + void operator-=(const Position& other) + { + x -= other.x; + y -= other.y; + } + + void print() { printf("pos: %d %d\n", x, y); } +}; + +struct Bounds +{ + Position pos; + Size size; + + Position topLeft() { return {pos.x, pos.y}; } + Position topRight() { return {short(pos.x + size.width - 1), pos.y}; } + + Position bottomRight() + { + return {short(pos.x + size.width - 1), short(pos.y + size.height - 1)}; + } + + Position bottomLeft() + { + return {pos.x, short(pos.y + short(size.height - 1))}; + } + + void print() + { + printf("Bounds: p:{%d %d} s:{%d %d}\n", pos.x, pos.y, size.width, + size.height); + } +}; + +enum class VertAlignment +{ + TOP, + CENTER, + BOTTOM +}; + +enum class HorizAlignment +{ + LEFT, + CENTER, + RIGHT +}; + +enum class Interaction +{ + BTN_DOWN, + BTN_UP, + CLICK, + LONG_CLICK +}; + +/** + * @brief Base class for anything that can be drawn on the screen and interacted + * with + */ +class View +{ +public: + using OnInteractionListener = std::function<void(View*, Interaction)>; + + View() {} + virtual ~View() {} + + /** + * @brief Sets the bounds in which the view will be drawn + */ + virtual void setBounds(Bounds bounds) + { + this->bounds = bounds; + invalidate(); + } + + Bounds getBounds() { return bounds; } + + /** + * @brief Signal that what has been previously drawn is now invalid and has + * to be redrawn. + */ + void invalidate() + { + if (!invalidated) + { + invalidated = true; + } + } + + /** + * @brief Invalidate the view tree which has this view as a root + */ + void invalidateTree() + { + invalidate(); + + std::vector<View*> childs = getChilds(); + for (auto child : childs) + { + child->invalidateTree(); + } + } + + /** + * @brief Draw the view in its bounds + * + * @param dc Reference to a drawingcontext + */ + virtual void draw(mxgui::DrawingContext& dc) + { + if (invalidated) + { + invalidated = false; + + dc.clear(bounds.topLeft(), bounds.bottomRight(), + getBackgroundColor()); + + dc.drawRectangle(bounds.topLeft(), bounds.bottomRight(), + selected ? col_selected : getBackgroundColor()); + } + } + + /** + * @brief Returns the list of childs + * + * @return std::vector<View*> + */ + virtual std::vector<View*> getChilds() + { + // Default behavious is no childs + return {}; + } + + virtual void setBackgroundColor(mxgui::Color color) + { + if (color != col_bg) + { + col_bg = color; + invalidate(); + } + } + + virtual mxgui::Color getBackgroundColor() { return col_bg; } + + void setSelectedColor(mxgui::Color color) + { + col_selected = color; + invalidate(); + } + + void setSelectable(bool selectable) { this->selectable = selectable; } + + bool isSelectable() { return selectable; } + + void setSelected(bool selected) + { + this->selected = selected; + invalidate(); + } + + bool isSelected() { return selected && selectable; } + + void addOnInteractionListener(OnInteractionListener listener) + { + listeners.push_back(listener); + } + + virtual void performInteraction(Interaction action) + { + for (auto lst : listeners) + { + if (lst) + { + lst(this, action); + } + } + } + +protected: + bool isInvalidated() { return invalidated; } + +private: + mxgui::Color col_selected = mxgui::red; + mxgui::Color col_bg = mxgui::black; + + std::vector<OnInteractionListener> listeners; + + bool selected = false; + bool selectable = false; + bool invalidated = true; + + Bounds bounds{{0, 0}, {0, 0}}; + + // Drawing big solid chunks (such as backgrounds) takes a lot of time: do + // not redraw if not necessary + bool was_selected = false; + mxgui::Color last_bg_color = col_bg; +}; diff --git a/src/shared/utils/testutils/MockGpioPin.h b/src/shared/utils/testutils/MockGpioPin.h new file mode 100644 index 0000000000000000000000000000000000000000..d3246d4d84183c77f1db9bd7a58db698a4733a4e --- /dev/null +++ b/src/shared/utils/testutils/MockGpioPin.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2021 Luca Erbetta + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <miosix.h> + +#include <memory> + +class MockGpioPin +{ +public: + MockGpioPin() + : val(new int(0)), + gpio_mode(new miosix::Mode::Mode_(miosix::Mode::OUTPUT)) + { + } + + void mode(miosix::Mode::Mode_ m) { *gpio_mode = m; } + + void high() { *val = 1; } + + void low() { *val = 0; } + + int value() { return *val; } + + // shared_ptr: make all copies of this instance share the same val / + // gpio_mode + std::shared_ptr<int> val; + std::shared_ptr<miosix::Mode::Mode_> gpio_mode; +}; \ No newline at end of file diff --git a/src/shared/utils/testutils/ThroughputCalculator.h b/src/shared/utils/testutils/ThroughputCalculator.h new file mode 100644 index 0000000000000000000000000000000000000000..49b59cfe59d5dc07bdd15535a0a275893b3817a1 --- /dev/null +++ b/src/shared/utils/testutils/ThroughputCalculator.h @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <miosix.h> + +#include <deque> +#include <limits> + +using miosix::FastMutex; +using miosix::Lock; +using std::deque; + +struct DataRateResult +{ + float data_rate; + float packet_loss; + float packets_per_second; +}; + +/** + * Helper class for calculating the throughput of communications. Just + * pass the size of a packet to addPacket(...) as soon as the packet is + * received. + */ +class ThroughputCalculator +{ +public: + /** + * @brief Creates a new ThroughputCalculator + * + * @param expected_pkt_interval Expected packet delivery interval for packet + * loss estimation + * @param window_duration Duration of the observation window. Longer windows + * means more stable values but slow response to abrupt changes + */ + ThroughputCalculator(unsigned int expected_pkt_interval, + unsigned int window_duration = 2000) + : expected_pkt_interval(expected_pkt_interval), + window_duration(window_duration) + { + } + + /** + * @brief Signal that a new packet has arrived. Call this as soon as the + * packet is available + * + * @param packet_size The size of the packet + */ + void addPacket(size_t packet_size) + { + Lock<FastMutex> lock(mutex_pkt); + + long long ts = miosix::getTick(); + + unsigned int interval = 0; + if (packets.size() > 0) + { + interval = static_cast<unsigned int>(ts - packets.back().timestamp); + } + + packets.push_back({ts, packet_size, interval}); + + removeOldPackets(ts); + } + + /** + * @brief Returns the datarate in Bytes/second averaged over the duration of + * the window + */ + float getDataRate() + { + Lock<FastMutex> lock(mutex_pkt); + + long long ts = miosix::getTick(); + removeOldPackets(ts); + + float sum = 0; + for (size_t i = 0; i < packets.size(); i++) + { + sum += packets[i].size; + } + return sum / (window_duration / 1000.0f); + } + + /** + * @brief Returns the packet loss percentage ([0-1]) based on the expected packet + * delivery interval + */ + float getPacketLoss() + { + Lock<FastMutex> lock(mutex_pkt); + long long ts = miosix::getTick(); + removeOldPackets(ts); + + float avg_interval = std::numeric_limits<float>::infinity(); + if (packets.size() > 0) + { + avg_interval = packets[0].interval; + for (size_t i = 1; i < packets.size(); i++) + { + avg_interval += packets[i].interval; + } + + avg_interval /= packets.size(); + } + + return 1 - expected_pkt_interval / avg_interval; + } + + /** + * @brief Returns the pps value averaged over the duration of the window + */ + float getPacketsPerSecond() + { + Lock<FastMutex> lock(mutex_pkt); + long long ts = miosix::getTick(); + removeOldPackets(ts); + + return (float)packets.size() / (window_duration / 1000.0f); + } + + DataRateResult getResult() + { + DataRateResult r; + r.data_rate = getDataRate(); + r.packet_loss = getPacketLoss(); + r.packets_per_second = getPacketsPerSecond(); + + return r; + } + +private: + struct Packet + { + long long timestamp; + size_t size; + unsigned int interval; + }; + + void removeOldPackets(long long ts) + { + while (packets.size() > 0 && + packets.front().timestamp < ts - window_duration) + { + packets.pop_front(); + } + } + + unsigned int expected_pkt_interval; + unsigned int window_duration; + deque<Packet> packets; + + FastMutex mutex_pkt; +}; \ No newline at end of file diff --git a/src/tests/catch/spidriver/FakeSPIBus.h b/src/tests/catch/spidriver/FakeSPIBus.h deleted file mode 100644 index d1213bfd6e6f9441e453cb84eb68e631572ca863..0000000000000000000000000000000000000000 --- a/src/tests/catch/spidriver/FakeSPIBus.h +++ /dev/null @@ -1,266 +0,0 @@ -/** - * Copyright (c) 2020 Skyward Experimental Rocketry - * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "FakeSpiTypedef.h" -#include "drivers/spi/SPIBusInterface.h" - -#pragma once - -/** - * @brief SPIBus modified to accept a fake Chip select & spi peripheral struct. - * Not ideal as modifications to SPIBus have to be manually applied here too, - * but that's the only way I found to test it effectively - */ -class FakeSPIBus : public SPIBusInterface -{ -public: - /** - * @brief Instantiates a new FakeSPIBus - * - * @param spi Pointer to the SPI peripheral to be used - */ - FakeSPIBus(FakeSpiTypedef* spi); - ~FakeSPIBus() {} - - // Delete copy/move contructors/operators - FakeSPIBus(const FakeSPIBus&) = delete; - FakeSPIBus& operator=(const FakeSPIBus&) = delete; - - FakeSPIBus(FakeSPIBus&&) = delete; - FakeSPIBus& operator=(FakeSPIBus&&) = delete; - - void disableBusConfiguration() { config_enabled = false; } - - /** - * @brief See SPIBusInterface::write() - */ - void write(uint8_t byte) override; - - /** - * @brief See SPIBusInterface::write() - */ - void write(uint8_t* data, size_t size) override; - - /** - * @brief See SPIBusInterface::read() - */ - uint8_t read() override; - - /** - * @brief See SPIBusInterface::read() - */ - void read(uint8_t* data, size_t size) override; - - /** - * @brief See SPIBusInterface::transfer() - */ - uint8_t transfer(uint8_t data) override; - - /** - * @brief See SPIBusInterface::transfer() - */ - void transfer(uint8_t* data, size_t size) override; - - /** - * @brief See SPIBusInterface::select() - */ - void select(GpioPin& cs) override; - - /** - * @brief See SPIBusInterface::deselect() - */ - void deselect(GpioPin& cs) override; - - /** - * @brief See SPIBusInterface::configure() - */ - void configure(SPIBusConfig config) override; - -private: - /** - * Writes a single byte on the SPI bus. - * - * @param byte Pointer to the byte to be written - */ - void write(uint8_t* byte); - - /** - * Reads a single byte from the SPI bus. - * - * @param byte Pointer to the byte where the read data will be stored - */ - void read(uint8_t* byte); - - /** - * Full duplex transfer. Writes a single byte on the SPI bus and replaces - * its content with the received data - * - * @param byte Pointer to the byte to be transfered - */ - void transfer(uint8_t* byte); - - FakeSpiTypedef* spi; - - SPIBusConfig config; - bool config_enabled = true; - bool first_config_applied = false; -}; - -FakeSPIBus::FakeSPIBus(FakeSpiTypedef* spi) : spi(spi) {} - -void FakeSPIBus::configure(SPIBusConfig new_config) -{ - // Reconfigure the bus only if config enabled. Do not reconfigure if already - // in the correct configuration. - if (config_enabled && (!first_config_applied || new_config != config)) - { - first_config_applied = true; - config = new_config; - - // Clean CR1 - spi->CR1 = 0; - - // Configure CPOL & CPHA bits - spi->CR1 |= static_cast<uint32_t>(config.mode); - - // Configure clock division (BR bits) - spi->CR1 |= static_cast<uint32_t>(config.clock_div); - - // Configure LSBFIRST bit - spi->CR1 |= static_cast<uint32_t>(config.bit_order); - - spi->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM // Use software chip-select - | SPI_CR1_MSTR // Master mode - | SPI_CR1_SPE; // Enable SPI - } -} - -inline void FakeSPIBus::write(uint8_t data) { write(&data); } - -inline void FakeSPIBus::write(uint8_t* data, size_t size) -{ - for (size_t i = 0; i < size; i++) - { - write(data + i); - } -} - -inline uint8_t FakeSPIBus::read() -{ - uint8_t data; - read(&data); - - return data; -} - -inline void FakeSPIBus::read(uint8_t* data, size_t size) -{ - for (size_t i = 0; i < size; i++) - { - read(data + i); - } -} - -inline uint8_t FakeSPIBus::transfer(uint8_t data) -{ - transfer(&data); - return data; -} - -inline void FakeSPIBus::transfer(uint8_t* data, size_t size) -{ - for (size_t i = 0; i < size; i++) - { - transfer(data + i); - } -} - -inline void FakeSPIBus::select(GpioPin& rcs) -{ - FakeGpioPin& cs = static_cast<FakeGpioPin&>(rcs); - - cs.low(); - if (config.cs_setup_time_us > 0) - { - delayUs(config.cs_setup_time_us); - } -} - -inline void FakeSPIBus::deselect(GpioPin& rcs) -{ - FakeGpioPin& cs = static_cast<FakeGpioPin&>(rcs); - - if (config.cs_hold_time_us > 0) - { - delayUs(config.cs_hold_time_us); - } - cs.high(); -} - -inline void FakeSPIBus::write(uint8_t* byte) -{ - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - // Write the byte in the transmit buffer - spi->DR = *byte; - - // Wait until byte is transmitted - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Clear the RX buffer by accessing the DR register - (void)spi->DR; -} - -inline void FakeSPIBus::transfer(uint8_t* byte) -{ - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - // Write the byte in the transmit buffer - spi->DR = *byte; - - // Wait until byte is transmitted - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Store the received data in the byte - *byte = (uint8_t)spi->DR; -} - -inline void FakeSPIBus::read(uint8_t* byte) -{ - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - // Write the byte in the transmit buffer - spi->DR = 0; - - // Wait until byte is transmitted - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Store the received data in the byte - *byte = (uint8_t)spi->DR; -} \ No newline at end of file diff --git a/src/tests/catch/spidriver/test-spidriver.cpp b/src/tests/catch/spidriver/test-spidriver.cpp index e0facfd1b7da9cce148af88ea0ebe7075d605c3f..26773eed2eb3b7050dd0e711d406656ec04fd177 100644 --- a/src/tests/catch/spidriver/test-spidriver.cpp +++ b/src/tests/catch/spidriver/test-spidriver.cpp @@ -25,11 +25,16 @@ #include "../catch-tests-entry.cpp" #endif +#ifndef USE_MOCK_PERIPHERALS +#error "This test requires SpiBusInterface built with MockGpioPin (-DUSE_MOCK_PERIPHERALS)" +#endif + #include <utils/testutils/catch.hpp> -#include "FakeSPIBus.h" -#include "drivers/spi/MockSPIBus.h" +#include "drivers/spi/SPIBus.h" #include "drivers/spi/SPIDriver.h" +#include "drivers/spi/test/FakeSpiTypedef.h" +#include "drivers/spi/test/MockSPIBus.h" template <typename T1, typename T2> bool bufcmp(T1* buf1, T2* buf2, size_t size) @@ -49,7 +54,7 @@ TEST_CASE("SPIBus - Bus Configuration") { FakeSpiTypedef spi; - FakeSPIBus bus{&spi}; + SPIBus bus{&spi}; REQUIRE(spi.CR1 == 0); @@ -62,79 +67,93 @@ TEST_CASE("SPIBus - Bus Configuration") { config.mode = SPIMode::MODE0; uint32_t expected_CR1 = 0x0344; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.mode = SPIMode::MODE1; expected_CR1 = 0x0345; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.mode = SPIMode::MODE2; expected_CR1 = 0x0346; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.mode = SPIMode::MODE3; expected_CR1 = 0x0347; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); } SECTION("Clock Divider") { config.clock_div = SPIClockDivider::DIV2; uint32_t expected_CR1 = 0x0344; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.clock_div = SPIClockDivider::DIV4; expected_CR1 = 0x034C; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.clock_div = SPIClockDivider::DIV8; expected_CR1 = 0x0354; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.clock_div = SPIClockDivider::DIV16; expected_CR1 = 0x035C; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.clock_div = SPIClockDivider::DIV32; expected_CR1 = 0x0364; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.clock_div = SPIClockDivider::DIV64; expected_CR1 = 0x036C; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.clock_div = SPIClockDivider::DIV128; expected_CR1 = 0x0374; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.clock_div = SPIClockDivider::DIV256; expected_CR1 = 0x037C; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); } SECTION("Bit order") { config.bit_order = SPIBitOrder::MSB_FIRST; uint32_t expected_CR1 = 0x0344; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); config.bit_order = SPIBitOrder::LSB_FIRST; expected_CR1 = 0x03C4; - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == expected_CR1); + bus.release(); } } @@ -147,8 +166,9 @@ TEST_CASE("SPIBus - Bus Configuration") config.bit_order = SPIBitOrder::LSB_FIRST; bus.disableBusConfiguration(); - bus.configure(config); + bus.acquire(config); REQUIRE(spi.CR1 == 0); + bus.release(); } } @@ -156,7 +176,7 @@ TEST_CASE("SPIBus - Chip select") { FakeSpiTypedef spi; - FakeSPIBus bus{&spi}; + SPIBus bus{&spi}; REQUIRE(spi.cs.value() == 1); @@ -174,7 +194,7 @@ TEST_CASE("SPIBus - One byte operations") spi.DR.in_buf = {1, 2, 3, 4, 5, 6, 7, 8, 9}; spi.CR1_expected = 0x03DF; - FakeSPIBus bus{&spi}; + SPIBus bus{&spi}; SPIBusConfig config; config.clock_div = SPIClockDivider::DIV16; @@ -182,7 +202,7 @@ TEST_CASE("SPIBus - One byte operations") config.mode = SPIMode::MODE3; config.bit_order = SPIBitOrder::LSB_FIRST; - bus.configure(config); + bus.acquire(config); bus.select(spi.cs); SECTION("Write") @@ -216,6 +236,9 @@ TEST_CASE("SPIBus - One byte operations") REQUIRE(spi.DR.out_buf.back() == 255); REQUIRE(spi.DR.out_buf.size() == 2); } + + bus.deselect(spi.cs); + bus.release(); } TEST_CASE("SPIBus - Multi byte operations") @@ -225,7 +248,7 @@ TEST_CASE("SPIBus - Multi byte operations") spi.DR.in_buf = {100, 101, 102, 103, 104, 105, 106, 107, 108}; spi.CR1_expected = 0x03DF; - FakeSPIBus bus{&spi}; + SPIBus bus{&spi}; SPIBusConfig config; config.clock_div = SPIClockDivider::DIV16; @@ -233,7 +256,7 @@ TEST_CASE("SPIBus - Multi byte operations") config.mode = SPIMode::MODE3; config.bit_order = SPIBitOrder::LSB_FIRST; - bus.configure(config); + bus.acquire(config); bus.select(spi.cs); // 2 identical buffers @@ -293,30 +316,33 @@ TEST_CASE("SPIBus - Multi byte operations") // No overflows REQUIRE(bufcmp(bufc + 4, buf + 4, 1)); } + + bus.deselect(spi.cs); + bus.release(); } TEST_CASE("SPITransaction - writes") { - MockSPIBus bus{}; SPIBusConfig config1{}; config1.mode = SPIMode::MODE1; config1.clock_div = SPIClockDivider::DIV32; - bus.expected_config = config1; + MockSPIBus bus(config1); + MockGpioPin cs; SECTION("Transaction") { - SPITransaction spi(bus, GpioPin(GPIOA_BASE, 1), config1); + SPITransaction spi(bus, cs, config1); - REQUIRE(bus.out_buf.size() == 0); + REQUIRE(bus.getOutBuf().size() == 0); SECTION("cmd write") { spi.write(9); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 1); - REQUIRE(bus.out_buf.back() == 9); + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf().back() == 9); } SECTION("1 byte reg write") @@ -324,9 +350,9 @@ TEST_CASE("SPITransaction - writes") spi.write(10, 77); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 2); - REQUIRE(bus.out_buf[0] == 10); - REQUIRE(bus.out_buf[1] == 77); + REQUIRE(bus.getOutBuf().size() == 2); + REQUIRE(bus.getOutBuf()[0] == 10); + REQUIRE(bus.getOutBuf()[1] == 77); } SECTION("multi byte reg write") @@ -338,8 +364,8 @@ TEST_CASE("SPITransaction - writes") spi.write(10, buf, 0); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 1); - REQUIRE(bus.out_buf[0] == 10); + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf()[0] == 10); } SECTION("2 writes") @@ -347,18 +373,18 @@ TEST_CASE("SPITransaction - writes") spi.write(10, buf, 4); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 5); + REQUIRE(bus.getOutBuf().size() == 5); - REQUIRE(bus.out_buf[0] == 10); - REQUIRE(bufcmp(buf, bus.out_buf.data() + 1, 4)); + REQUIRE(bus.getOutBuf()[0] == 10); + REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 1, 4)); spi.write(99, buf, 6); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 12); + REQUIRE(bus.getOutBuf().size() == 12); - REQUIRE(bus.out_buf[5] == 99); - REQUIRE(bufcmp(buf, bus.out_buf.data() + 6, 6)); + REQUIRE(bus.getOutBuf()[5] == 99); + REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 6, 6)); } } @@ -369,65 +395,65 @@ TEST_CASE("SPITransaction - writes") spi.write(buf, 0); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 0); + REQUIRE(bus.getOutBuf().size() == 0); spi.write(buf, 4); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 4); + REQUIRE(bus.getOutBuf().size() == 4); - REQUIRE(bufcmp(buf, bus.out_buf.data(), 4)); + REQUIRE(bufcmp(buf, bus.getOutBuf().data(), 4)); spi.write(buf, 6); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 10); + REQUIRE(bus.getOutBuf().size() == 10); - REQUIRE(bufcmp(buf, bus.out_buf.data() + 4, 6)); + REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 4, 6)); } } } TEST_CASE("SPITransaction - reads") { - MockSPIBus bus; - - bus.in_buf = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109}; - SPIBusConfig config1; config1.mode = SPIMode::MODE1; config1.clock_div = SPIClockDivider::DIV32; - bus.expected_config = config1; + MockSPIBus bus(config1); + MockGpioPin cs; + + uint8_t in_data[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109}; + bus.push(in_data, 10); SECTION("Transaction") { - SPISlave slave(bus, GpioPin(GPIOA_BASE, 1), config1); + SPISlave slave(bus, cs, config1); SPITransaction spi(slave); - REQUIRE(bus.out_buf.size() == 0); + REQUIRE(bus.getOutBuf().size() == 0); SECTION("1 byte reg read") { - REQUIRE(spi.read(0x05) == bus.in_buf[0]); + REQUIRE(spi.read(0x05) == in_data[0]); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 1); - REQUIRE(bus.out_buf.back() == 0x85); + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf().back() == 0x85); - REQUIRE(spi.read(0x05, true) == bus.in_buf[1]); + REQUIRE(spi.read(0x05, true) == in_data[1]); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 2); - REQUIRE(bus.out_buf.back() == 0x85); + REQUIRE(bus.getOutBuf().size() == 2); + REQUIRE(bus.getOutBuf().back() == 0x85); - REQUIRE(spi.read(0x05, false) == bus.in_buf[2]); + REQUIRE(spi.read(0x05, false) == in_data[2]); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 3); - REQUIRE(bus.out_buf.back() == 0x05); + REQUIRE(bus.getOutBuf().size() == 3); + REQUIRE(bus.getOutBuf().back() == 0x05); } SECTION("multi byte reg read") @@ -438,29 +464,29 @@ TEST_CASE("SPITransaction - reads") spi.read(0x05, buf, 0); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 1); - REQUIRE(bus.out_buf.back() == 0x85); + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf().back() == 0x85); REQUIRE(bufcmp(buf, cmp, buf_size)); spi.read(0x05, buf, 3); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 2); - REQUIRE(bus.out_buf.back() == 0x85); - REQUIRE(bufcmp(buf, bus.in_buf.data(), 3)); + REQUIRE(bus.getOutBuf().size() == 2); + REQUIRE(bus.getOutBuf().back() == 0x85); + REQUIRE(bufcmp(buf, &in_data[0], 3)); REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3)); spi.read(0x05, buf, 3, true); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 3); - REQUIRE(bus.out_buf.back() == 0x85); - REQUIRE(bufcmp(buf, bus.in_buf.data() + 3, 3)); + REQUIRE(bus.getOutBuf().size() == 3); + REQUIRE(bus.getOutBuf().back() == 0x85); + REQUIRE(bufcmp(buf, &in_data[3], 3)); REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3)); spi.read(0x05, buf, 4, false); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 4); - REQUIRE(bus.out_buf.back() == 0x05); - REQUIRE(bufcmp(buf, bus.in_buf.data() + 6, 4)); + REQUIRE(bus.getOutBuf().size() == 4); + REQUIRE(bus.getOutBuf().back() == 0x05); + REQUIRE(bufcmp(buf,&in_data[6], 4)); REQUIRE(bufcmp(buf + 4, cmp + 4, buf_size - 4)); } @@ -472,13 +498,13 @@ TEST_CASE("SPITransaction - reads") spi.read(buf, 0); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 0); + REQUIRE(bus.getOutBuf().size() == 0); REQUIRE(bufcmp(buf, cmp, buf_size)); spi.read(buf, 3); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 0); - REQUIRE(bufcmp(buf, bus.in_buf.data(), 3)); + REQUIRE(bus.getOutBuf().size() == 0); + REQUIRE(bufcmp(buf, &in_data[0], 3)); REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3)); } } @@ -486,20 +512,21 @@ TEST_CASE("SPITransaction - reads") TEST_CASE("SPITransaction - transfer") { - MockSPIBus bus; - - bus.in_buf = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - SPIBusConfig config1; config1.mode = SPIMode::MODE1; config1.clock_div = SPIClockDivider::DIV32; - bus.expected_config = config1; + MockSPIBus bus(config1); + MockGpioPin cs; + + uint8_t data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + bus.push(data, 10); SECTION("Transaction") { - SPISlave slave(bus, GpioPin(GPIOA_BASE, 1), config1); + SPISlave slave(bus, cs, config1); SPITransaction spi(slave); const int buf_size = 7; @@ -508,14 +535,14 @@ TEST_CASE("SPITransaction - transfer") spi.transfer(buf, 0); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 0); + REQUIRE(bus.getOutBuf().size() == 0); REQUIRE(bufcmp(buf, cmp, buf_size)); spi.transfer(buf, 4); REQUIRE_FALSE(bus.isSelected()); - REQUIRE(bus.out_buf.size() == 4); - REQUIRE(bufcmp(buf, bus.in_buf.data(), 4)); - REQUIRE(bufcmp(cmp, bus.out_buf.data(), 4)); + REQUIRE(bus.getOutBuf().size() == 4); + REQUIRE(bufcmp(buf, &data[0], 4)); + REQUIRE(bufcmp(cmp, bus.getOutBuf().data(), 4)); REQUIRE(bufcmp(buf + 4, cmp + 4, buf_size - 4)); } } \ No newline at end of file diff --git a/src/tests/catch/xbee/MockXbeeSPIBus.h b/src/tests/catch/xbee/MockXbeeSPIBus.h new file mode 100644 index 0000000000000000000000000000000000000000..2c23b4fd810ff53d48dc74944a05cf89073005ab --- /dev/null +++ b/src/tests/catch/xbee/MockXbeeSPIBus.h @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <deque> +#include <functional> +#include <memory> + +#include "drivers/Xbee/APIFrameParser.h" +#include "drivers/Xbee/APIFrames.h" +#include "drivers/spi/test/MockSPIBus.h" +#include "utils/testutils/MockGpioPin.h" + +using std::deque; +using std::function; +using std::unique_ptr; + +class MockXbeeSPIBus : public MockSPIBus +{ +public: + MockXbeeSPIBus(SPIBusConfig expected_config, MockGpioPin& attn) + : MockSPIBus(expected_config), attn(attn) + { + } + + void registerCallback(function<void(MockGpioPin&)> callback) + { + this->callback = callback; + } + + void removeCallback() { this->callback = nullptr; } + + void pushApiFrame(Xbee::APIFrame& api) + { + unique_ptr<uint8_t> bytes(new uint8_t[Xbee::MAX_API_FRAME_SIZE]); + + size_t len = api.toBytes(bytes.get()); + push(bytes.get(), len); + } + + deque<Xbee::APIFrame> getParsedFrames() + { + Lock<FastMutex> l(mutex); + return parsed_frames; + } + + /** + * @brief Wether to generate a tx_status responde upon receiving a tx + * request + * + */ + void setRespondWithTxStatus(bool respond, uint8_t delivery_status = 0) + { + tx_status_delivery_status = delivery_status; + respond_with_tx_status = respond; + } + +protected: + void _push(uint8_t* data, size_t len) override + { + MockSPIBus::_push(data, len); + + assertATTN(); + } + + uint8_t _read() override + { + uint8_t r = MockSPIBus::_read(); + + if (in_buf_pos_cntr == in_buf.size()) + { + resetATTN(); + } + + return r; + } + + void _write(uint8_t byte) override + { + MockSPIBus::_write(byte); + + Xbee::APIFrameParser::ParseResult res = + parser.parse(byte, &parsing_frame); + + if (res == Xbee::APIFrameParser::ParseResult::SUCCESS) + { + parsed_frames.push_back(parsing_frame); + + if (parsing_frame.frame_type == Xbee::FTYPE_TX_REQUEST && + respond_with_tx_status) + { + Xbee::TXRequestFrame* tx_req = + parsing_frame.toFrameType<Xbee::TXRequestFrame>(); + Xbee::TXStatusFrame tx_stat; + + tx_stat.setFrameID(tx_req->getFrameID()); + tx_stat.setDeliveryStatus(tx_status_delivery_status); + tx_stat.setDiscoveryStatus(0); + tx_stat.setTransmitRetryCount(0); + tx_stat.calcChecksum(); + + _pushApiFrame(tx_stat); + } + } + else if (res == Xbee::APIFrameParser::ParseResult::FAIL) + { + printf("[MockXbeeSPI] Failed parsing frame written to SPI bus\n"); + } + } + +private: + void _pushApiFrame(Xbee::APIFrame& api) + { + uint8_t* bytes = new uint8_t[Xbee::MAX_API_FRAME_SIZE]; + size_t len = api.toBytes(bytes); + _push(bytes, len); + + delete[] bytes; + } + + void assertATTN() + { + if (attn.value() != 0) + { + attn.low(); + if (callback) + callback(attn); + } + } + + void resetATTN() + { + if (attn.value() == 0) + { + attn.high(); + if (callback) + callback(attn); + } + } + + deque<Xbee::APIFrame> parsed_frames; + + Xbee::APIFrameParser parser; + Xbee::APIFrame parsing_frame; + + function<void(MockGpioPin&)> callback; + MockGpioPin attn; + + bool respond_with_tx_status = true; + uint8_t tx_status_delivery_status = 0; +}; \ No newline at end of file diff --git a/src/tests/catch/xbee/test-xbee-driver.cpp b/src/tests/catch/xbee/test-xbee-driver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..71aa6b54e833e0cd460616320dd7aefe5b3860d5 --- /dev/null +++ b/src/tests/catch/xbee/test-xbee-driver.cpp @@ -0,0 +1,415 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef STANDALONE_CATCH1_TEST +#include "../catch-tests-entry.cpp" +#endif +#include <ctime> +#include <future> +#include <memory> + +#include "MockXbeeSPIBus.h" +#include "drivers/Xbee/Xbee.h" +#include "drivers/spi/SPIDriver.h" +#include "utils/testutils/catch.hpp" + +using namespace Xbee; +using std::async; +using std::unique_ptr; + +using uint8_ptr = unique_ptr<uint8_t>; + +/** + * @brief Generates a sequence of incremental bytes + */ +uint8_ptr incrementalBytes(size_t length, uint8_t start_from = 0) +{ + uint8_ptr bytes(new uint8_t[length]); + + for (size_t i = 0; i < length; i++) + { + bytes.get()[i] = (start_from + i) % 256; + } + + return bytes; +} + +class XbeeWrapper +{ +public: + XbeeWrapper(unsigned int tx_timeout) + : bus(new MockXbeeSPIBus(xbee_cfg, attn)), + xbee(new Xbee::Xbee(*bus.get(), xbee_cfg, cs, attn, rst, tx_timeout)) + { + attn.high(); + } + + MockGpioPin cs; + MockGpioPin attn; + + MockGpioPin rst; + + SPIBusConfig xbee_cfg{}; + + unique_ptr<MockXbeeSPIBus> bus; + unique_ptr<Xbee::Xbee> xbee; +}; + +TEST_CASE("[Xbee] Test xbee.send(...) by itself") +{ + uint8_ptr pkt = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1); + uint8_ptr pkt_orig = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1); + + XbeeWrapper wrap(DEFAULT_TX_TIMEOUT); + + SECTION("Middle payload length") + { + REQUIRE(wrap.xbee->send(pkt.get(), 128)); + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + + REQUIRE(wrap.bus->getParsedFrames().front().frame_type == + Xbee::FTYPE_TX_REQUEST); + + TXRequestFrame tx_req = *wrap.bus->getParsedFrames() + .front() + .toFrameType<Xbee::TXRequestFrame>(); + + REQUIRE(tx_req.getRFDataLength() == 128); + REQUIRE(memcmp(tx_req.getRFDataPointer(), pkt_orig.get(), 128) == 0); + } + + SECTION("Max payload length") + { + REQUIRE(wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH)); + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + + REQUIRE(wrap.bus->getParsedFrames().front().frame_type == + Xbee::FTYPE_TX_REQUEST); + + TXRequestFrame tx_req = *wrap.bus->getParsedFrames() + .front() + .toFrameType<Xbee::TXRequestFrame>(); + + REQUIRE(tx_req.getRFDataLength() == Xbee::MAX_PACKET_PAYLOAD_LENGTH); + REQUIRE(memcmp(tx_req.getRFDataPointer(), pkt_orig.get(), + Xbee::MAX_PACKET_PAYLOAD_LENGTH) == 0); + } + + SECTION("Oversize payload") + { + REQUIRE_FALSE( + wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1)); + REQUIRE(wrap.bus->getParsedFrames().size() == 0); + } + + SECTION("No payload") + { + REQUIRE_FALSE(wrap.xbee->send(pkt.get(), 0)); + REQUIRE(wrap.bus->getParsedFrames().size() == 0); + } + + SECTION("Send error") + { + wrap.bus->setRespondWithTxStatus(true, DELS_NO_SPECTRUM_AVAILABLE); + + REQUIRE_FALSE( + wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH)); + } + + SECTION("TX status timeout") + { + wrap.bus->setRespondWithTxStatus(false); + + long long start = miosix::getTick(); + + REQUIRE_FALSE( + wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH)); + + // Should not have returned until the timeout has expired + REQUIRE(miosix::getTick() >= start + DEFAULT_TX_TIMEOUT); + } +} + +TEST_CASE("[Xbee] Test xbee.receive(...) by itself") +{ + uint8_ptr rx_buf(new uint8_t[MAX_PACKET_PAYLOAD_LENGTH]); + + uint8_ptr pkt = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH); + + XbeeWrapper wrap(DEFAULT_TX_TIMEOUT); + + RXPacketFrame rx; + rx.setRXDataLength(MAX_PACKET_PAYLOAD_LENGTH); + memcpy(rx.getRXDataPointer(), pkt.get(), MAX_PACKET_PAYLOAD_LENGTH); + rx.setReceiveOptions(RO_POINT_MULTIPOINT); + rx.setSourceAddress(0x1122334455667788); + + SECTION("RX buffer bigger than RX packet payload") + { + rx.setRXDataLength(50); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->receive(rx_buf.get(), MAX_PACKET_PAYLOAD_LENGTH) == + 50); + + REQUIRE(memcmp(rx_buf.get(), pkt.get(), 50) == 0); + } + + SECTION("RX buffer smaller than RX packet payload") + { + rx.setRXDataLength(130); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 50); + REQUIRE(wrap.xbee->receive(rx_buf.get() + 50, 50) == 50); + REQUIRE(wrap.xbee->receive(rx_buf.get() + 100, 50) == 30); + + REQUIRE(memcmp(rx_buf.get(), pkt.get(), 130) == 0); + } + + SECTION("RX buffer == 1") + { + rx.setRXDataLength(30); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + + for (int i = 0; i < 30; i++) + { + REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1); + } + + REQUIRE(memcmp(rx_buf.get(), pkt.get(), 30) == 0); + } + + SECTION("RX packet with wrong checksum") + { + rx.setRXDataLength(20); + rx.calcChecksum(); + wrap.bus->pushApiFrame(rx); + + rx.setRXDataLength(21); + rx.calcChecksum(); + rx.setRXDataLength(20); // Now the checksum is wrong + wrap.bus->pushApiFrame(rx); + + // Only receive one of the two frames + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 20); + + auto dont_care = async(std::launch::async, [&]() { + miosix::Thread::sleep(1000); + wrap.xbee->wakeReceiver(true); + }); + + // This should block until we force it to return + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == -1); + } + + SECTION("receive() blocks until an interrupt is received") + { + rx.setRXDataLength(20); + rx.calcChecksum(); + + // Send an rx frame in one second + auto dont_care = async(std::launch::async, [&]() { + miosix::Thread::sleep(1000); + wrap.bus->pushApiFrame(rx); + wrap.xbee->wakeReceiver(); + }); + + long long start = miosix::getTick(); + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 20); + + // Should not have returned before we sent the rx frame + REQUIRE(miosix::getTick() >= start + 1000); + } +} + +TEST_CASE("[Xbee] Receive while sending") +{ + uint8_ptr rx_buf(new uint8_t[MAX_PACKET_PAYLOAD_LENGTH]); + + uint8_ptr pkt_rx = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH); + uint8_ptr pkt_tx = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH); + + XbeeWrapper wrap(1000); + wrap.bus->setRespondWithTxStatus(true, 0); + + RXPacketFrame rx; + rx.setRXDataLength(MAX_PACKET_PAYLOAD_LENGTH); + memcpy(rx.getRXDataPointer(), pkt_rx.get(), MAX_PACKET_PAYLOAD_LENGTH); + rx.setReceiveOptions(RO_POINT_MULTIPOINT); + rx.setSourceAddress(0x1122334455667788); + + int num_rx_while_tx = 0; + + wrap.xbee->setOnFrameReceivedListener([&](APIFrame& api) { + if (api.frame_type == FTYPE_RX_PACKET_FRAME) + { + ++num_rx_while_tx; + } + }); + + SECTION("2 RX packet smaller than one TX packet") + { + rx.setRXDataLength(50); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->send(pkt_tx.get(), 150)); + + REQUIRE(num_rx_while_tx == 2); + + size_t rx_len = 0; + while (rx_len < 100) + { + rx_len += wrap.xbee->receive(rx_buf.get() + rx_len, 200); + } + + REQUIRE(rx_len == 100); + REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0); + REQUIRE(memcmp(rx_buf.get() + 50, pkt_rx.get(), 50) == 0); + } + + SECTION("More RX packets than buffer size: drop the oldest") + { + rx.setRXDataLength(10); + + // 1 packet too many + unsigned int numpkts = RX_FRAMES_BUF_SIZE + 1; + for (unsigned int i = 0; i < numpkts; i++) + { + memcpy(rx.getRXDataPointer(), pkt_rx.get() + i, 10); + rx.calcChecksum(); + wrap.bus->pushApiFrame(rx); + } + + // Should receive all the packets while sending + REQUIRE(wrap.xbee->send(pkt_tx.get(), MAX_PACKET_PAYLOAD_LENGTH)); + + REQUIRE(num_rx_while_tx == numpkts); + + // Only receive the last three packets + size_t rx_len = 0; + while (rx_len < (numpkts - 1) * 10) + { + rx_len += wrap.xbee->receive(rx_buf.get() + rx_len, 200); + } + + REQUIRE(rx_len == (numpkts - 1) * 10); + + for (unsigned int i = 0; i < numpkts - 1; i++) + { + REQUIRE(memcmp(rx_buf.get() + i * 10, pkt_rx.get() + i + 1, 10) == + 0); + } + } + + SECTION("2 RX packet smaller than one TX packet, 1 byte receive") + { + rx.setRXDataLength(50); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->send(pkt_tx.get(), 150)); + + REQUIRE(num_rx_while_tx == 2); + + for (int i = 0; i < 50; i++) + { + REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1); + } + REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0); + + for (int i = 0; i < 50; i++) + { + REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1); + } + REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0); + } +} + +TEST_CASE("[Xbee] Test Xbee::sendAtCommand(...)") +{ + XbeeWrapper wrap(DEFAULT_TX_TIMEOUT); + + SECTION("AT Command, no response required") + { + wrap.xbee->sendATCommand("AB"); + + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND); + } + + SECTION("AT Command, response required but not received") + { + ATCommandResponseFrame response; + long long start = miosix::getTick(); + REQUIRE_FALSE( + wrap.xbee->sendATCommand("AB", &response, nullptr, 0, 1000)); + REQUIRE(miosix::getTick() >= start + 1000); + + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND); + } + + SECTION("AT Command, response required and received") + { + // Padding required in order to not receive the ATCommandResponse + // frame too early in testing + RXPacketFrame padding; + padding.setRXDataLength(50); + padding.calcChecksum(); + + ATCommandResponseFrame resp; + resp.setATCommand("AB"); + resp.setFrameID(1); + resp.setCommandDataSize(1); + resp.getCommandDataPointer()[0] = 0xAB; + resp.calcChecksum(); + + wrap.bus->pushApiFrame(padding); + wrap.bus->pushApiFrame(resp); + + ATCommandResponseFrame received_response; + long long start = miosix::getTick(); + + REQUIRE(wrap.xbee->sendATCommand("AB", &received_response, nullptr, 0, + 1000)); + REQUIRE(miosix::getTick() < start + 1000); + + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND); + + REQUIRE(received_response.getCommandDataLength() == 1); + REQUIRE(received_response.getCommandDataPointer()[0] == 0xAB); + } +} \ No newline at end of file diff --git a/src/tests/catch/xbee/test-xbee-parser.cpp b/src/tests/catch/xbee/test-xbee-parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..10c54ae87414c37f61c528f8902164fdbe817c23 --- /dev/null +++ b/src/tests/catch/xbee/test-xbee-parser.cpp @@ -0,0 +1,575 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Disable asserts +#define NDEBUG + +#include <cstdio> + +#ifdef STANDALONE_CATCH1_TEST +#include "../catch-tests-entry.cpp" +#endif + +#include <cstdio> +#include <cstring> + +#include "drivers/Xbee/APIFrameParser.h" +#include "utils/testutils/catch.hpp" + +using namespace Xbee; + +/** + * @brief Calculates the checksum for a sequence of bytes representing an API + * frame. + * + * @param frame bytes (including start delimiter, and checksum set to 0) + * @param frame_size size of the frame, including start delimiter and checksum + */ +void calcChecksum(uint8_t* frame, size_t frame_size) +{ + frame[frame_size - 1] = 0; + for (size_t i = 3; i < frame_size - 1; i++) + { + frame[frame_size - 1] += frame[i]; + } + frame[frame_size - 1] = 0xFF - frame[frame_size - 1]; +} + +void printHex(uint8_t* frame, size_t frame_size) +{ + for (size_t i = 0; i < frame_size; i++) + { + printf("%02X ", frame[i]); + } + printf("\n"); +} + +void printu64(uint64_t v) +{ + uint8_t* p = reinterpret_cast<uint8_t*>(&v); + + for (int i = 0; i < 8; i++) + { + printf("%02X ", p[i]); + } + + printf("\n"); +} + +void printBuf(uint8_t* buf, size_t size) +{ + for (size_t i = 0; i < size; i++) + { + printf("%02X ", buf[i]); + } + + printf("\n"); +} + +bool compareAPIFrames(const APIFrame& a, const APIFrame& b) +{ + bool result = a.start_del == b.start_del && a.frame_type == b.frame_type && + a.length == b.length && a.checksum == b.checksum; + + return result && + memcmp(a.frame_data, b.frame_data, a.getFrameDataLength()) == 0; +} + +APIFrame parse(APIFrameParser& parser, uint8_t* data, size_t len) +{ + APIFrame out; + for (size_t i = 0; i < len; i++) + { + APIFrameParser::ParseResult pres = parser.parse(data[i], &out); + + if (i < len - 1) + { + CAPTURE(i); + REQUIRE(pres == APIFrameParser::ParseResult::PARSING); + } + else + { + REQUIRE(pres == APIFrameParser::ParseResult::SUCCESS); + } + } + + REQUIRE(out.verifyChecksum()); + + return out; +} + +template <typename FrameType> +void testParse(APIFrameParser& parser, FrameType orig) +{ + uint8_t bytes[MAX_API_FRAME_SIZE]; + + size_t len = orig.toBytes(bytes); + + APIFrame parsed_api = parse(parser, bytes, len); + + REQUIRE(compareAPIFrames(orig, parsed_api)); +} + +// Taken from examples in the datasheet +TEST_CASE("Frame serialization") +{ + uint8_t bytes[MAX_API_FRAME_SIZE]; + APIFrame deserialized; + + SECTION("AT Command") + { + ATCommandFrame at_orig; + at_orig.setATCommand("NH"); + REQUIRE(memcmp(at_orig.getATCommand(), "NH", 2) == 0); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + at_orig.setParameterSize(2); + at_orig.getCommandDataPointer()[0] = 0x07; + at_orig.getCommandDataPointer()[1] = 0x17; + + REQUIRE(at_orig.getCommandDataLength() == 2); + REQUIRE(at_orig.getFrameDataLength() == 5); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 6, 0x08, 1, + 0x4E, 0x48, 0x07, 0x17, 0x00}; + + calcChecksum(expected_bytes, 10); + + size_t len = at_orig.toBytes(bytes); + + REQUIRE(len == 10); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("AT Command, No parameters") + { + ATCommandFrame at_orig; + + at_orig.setATCommand("NH"); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + REQUIRE(at_orig.getCommandDataLength() == 0); + REQUIRE(at_orig.getFrameDataLength() == 3); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 4, 0x08, 1, 0x4E, 0x48, 0x60}; + + size_t len = at_orig.toBytes(bytes); + + REQUIRE(len == 8); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("AT Command Response") + { + ATCommandResponseFrame at_orig; + at_orig.setATCommand("BD"); + REQUIRE(memcmp(at_orig.getATCommand(), "BD", 2) == 0); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + at_orig.setCommandDataSize(3); + at_orig.getCommandDataPointer()[0] = 0x07; + at_orig.getCommandDataPointer()[1] = 0x17; + at_orig.getCommandDataPointer()[2] = 0x27; + + REQUIRE(at_orig.getCommandDataLength() == 3); + REQUIRE(at_orig.getFrameDataLength() == 7); + + at_orig.setCommandStatus(0x55); + REQUIRE(at_orig.getCommandStatus() == 0x55); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 8, 0x88, 0x01, 0x42, + 0x44, 0x55, 0x07, 0x17, 0x27, 0}; + + calcChecksum(expected_bytes, 12); + + size_t len = at_orig.toBytes(bytes); + REQUIRE(len == 12); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("AT Command Response, No cmd data") + { + ATCommandResponseFrame at_orig; + at_orig.setATCommand("BD"); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + REQUIRE(at_orig.getCommandDataLength() == 0); + REQUIRE(at_orig.getFrameDataLength() == 4); + + at_orig.setCommandStatus(0x0); + REQUIRE(at_orig.getCommandStatus() == 0x00); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 5, 0x88, 0x01, + 0x42, 0x44, 0x00, 0xF0}; + + calcChecksum(expected_bytes, 9); + + size_t len = at_orig.toBytes(bytes); + REQUIRE(len == 9); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("TX Request Frame") + { + TXRequestFrame tx_orig; + + tx_orig.setFrameID(0x01); + REQUIRE(tx_orig.getFrameID() == 0x01); + + tx_orig.setDestAddress(0x0013A200400A0127); + + bool addr_cmp = tx_orig.getDestAddress() == 0x0013A200400A0127; + REQUIRE(addr_cmp); + + tx_orig.setBroadcastRadius(0x55); + REQUIRE(tx_orig.getBroadcastRadius() == 0x55); + + tx_orig.setTransmitOptions(0x40); + REQUIRE(tx_orig.getTrasmitOptions() == 0x40); + + uint8_t* rf_data = tx_orig.getRFDataPointer(); + + rf_data[0] = 0x54; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + rf_data[6] = 0x30; + rf_data[7] = 0x41; + + tx_orig.setRFDataLength(8); + + REQUIRE(tx_orig.getRFDataLength() == 8); + REQUIRE(tx_orig.getFrameDataLength() == 21); + + uint8_t expected_bytes[] = {0x7E, 0, 0x16, 0x10, 0x01, 0x00, 0x13, + 0xA2, 0x00, 0x40, 0x0A, 0x01, 0x27, 0xFF, + 0xFE, 0x55, 0x40, 0x54, 0x78, 0x44, 0x61, + 0x74, 0x61, 0x30, 0x41, 0x00}; + + // Datasheet example checksum is wrong + calcChecksum(expected_bytes, 26); + tx_orig.calcChecksum(); + size_t len = tx_orig.toBytes(bytes); + + REQUIRE(len == 26); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, tx_orig)); + } + + SECTION("TX Request Frame - No payload") + { + TXRequestFrame tx_orig; + + tx_orig.setFrameID(0x01); + REQUIRE(tx_orig.getFrameID() == 0x01); + + tx_orig.setDestAddress(0x0013A200400A0127); + bool addr_cmp = tx_orig.getDestAddress() == 0x0013A200400A0127; + REQUIRE(addr_cmp); + + tx_orig.setBroadcastRadius(0x55); + REQUIRE(tx_orig.getBroadcastRadius() == 0x55); + + tx_orig.setTransmitOptions(0x40); + REQUIRE(tx_orig.getTrasmitOptions() == 0x40); + + uint8_t* rf_data = tx_orig.getRFDataPointer(); + + REQUIRE(tx_orig.getRFDataLength() == 0); + REQUIRE(tx_orig.getFrameDataLength() == 13); + + tx_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 14, 0x10, 0x01, 0x00, + 0x13, 0xA2, 0x00, 0x40, 0x0A, 0x01, + 0x27, 0xFF, 0xFE, 0x55, 0x40, 0x00}; + + calcChecksum(expected_bytes, 18); + + size_t len = tx_orig.toBytes(bytes); + + REQUIRE(len == 18); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, tx_orig)); + } + + SECTION("Modem status frame") + { + ModemStatusFrame mod_orig; + + mod_orig.setStatus(0x55); + + mod_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 2, 0x8A, 0x55, 0x00}; + + calcChecksum(expected_bytes, 6); + + size_t len = mod_orig.toBytes(bytes); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, mod_orig)); + } + + SECTION("TX Status frame") + { + constexpr size_t frame_size = 11; + TXStatusFrame tx_orig{}; + + tx_orig.setFrameID(0x47); + REQUIRE(tx_orig.getFrameID() == 0x47); + tx_orig.setTransmitRetryCount(0x55); + REQUIRE(tx_orig.getTransmitRetryCount() == 0x55); + + tx_orig.setDeliveryStatus(0x44); + REQUIRE(tx_orig.getDeliveryStatus() == 0x44); + + tx_orig.setDiscoveryStatus(2); + REQUIRE(tx_orig.getDiscoveryStatus() == 2); + + tx_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 0x07, 0x8B, 0x47, 0xFF, + 0xFE, 0x55, 0x44, 0x02, 0x00}; + + calcChecksum(expected_bytes, 11); + + size_t len = tx_orig.toBytes(bytes); + REQUIRE(len == 11); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, tx_orig)); + } + + SECTION("RX Packet frame") + { + RXPacketFrame rx; + + rx.setSourceAddress(0x0013A20040522BAA); + bool addr_cmp = rx.getSourceAddress() == 0x0013A20040522BAA; + REQUIRE(addr_cmp); + + rx.setReceiveOptions(0x01); + REQUIRE(rx.getReceiveOptions() == 0x01); + + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + REQUIRE(rx.getRXDataLength() == 6); + REQUIRE(rx.getFrameDataLength() == 0x11); + + rx.calcChecksum(); + + uint8_t expected_bytes[] = { + 0x7E, 0, 0x12, 0x90, 0x00, 0x13, 0xA2, 0x00, 0x40, 0x52, 0x2B, + 0xAA, 0xFF, 0xFE, 0x01, 0x52, 0x78, 0x44, 0x61, 0x74, 0x61, 0x11}; + + // calcChecksum(expected_bytes, frame_size); + + size_t len = rx.toBytes(bytes); + + REQUIRE(len == 22); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, rx)); + } + + SECTION("RX Packet frame - no payload") + { + RXPacketFrame rx; + + rx.setSourceAddress(0x0013A20040522BAA); + bool addr_cmp = rx.getSourceAddress() == 0x0013A20040522BAA; + REQUIRE(addr_cmp); + + rx.setReceiveOptions(0x01); + REQUIRE(rx.getReceiveOptions() == 0x01); + + REQUIRE(rx.getRXDataLength() == 0); + REQUIRE(rx.getFrameDataLength() == 11); + + rx.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 12, 0x90, 0x00, 0x13, + 0xA2, 0x00, 0x40, 0x52, 0x2B, 0xAA, + 0xFF, 0xFE, 0x01, 0x00}; + + calcChecksum(expected_bytes, 16); + + size_t len = rx.toBytes(bytes); + + REQUIRE(len == 16); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + memset(deserialized.frame_data, 0, FRAME_DATA_SIZE); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, rx)); + } +} + +TEST_CASE("Frame parsing") +{ + APIFrameParser parser; + + SECTION("RXPacketFrame Parsing") + { + RXPacketFrame rx; + rx.setSourceAddress(0x0013A20040522BAA); + rx.setReceiveOptions(0x01); + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + rx.calcChecksum(); + + // Parse 3 times the same frame + testParse(parser, rx); + testParse(parser, rx); + testParse(parser, rx); + } +} + +TEST_CASE("Parser edge cases") +{ + APIFrameParser parser; + + SECTION("Wrong checksum") + { + RXPacketFrame rx; + rx.setSourceAddress(0x0013A20040522BAA); + rx.setReceiveOptions(0x01); + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + rx.calcChecksum(); + + uint8_t bytes[MAX_API_FRAME_SIZE]; + + size_t len = rx.toBytes(bytes); + + bytes[len - 1] += 1; + + APIFrame out; + for (size_t i = 0; i < len; i++) + { + APIFrameParser::ParseResult res = parser.parse(bytes[i], &out); + REQUIRE_FALSE(res == APIFrameParser::ParseResult::SUCCESS); + + if (i == len - 1) + { + REQUIRE(res == APIFrameParser::ParseResult::FAIL); + } + } + } + + SECTION("Wrong start delimiter") + { + RXPacketFrame rx; + rx.start_del = 0x55; + rx.setSourceAddress(0x0013A20040522BAA); + rx.setReceiveOptions(0x01); + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + rx.calcChecksum(); + + uint8_t bytes[MAX_API_FRAME_SIZE]; + + size_t len = rx.toBytes(bytes); + + APIFrame out; + for (size_t i = 0; i < len; i++) + { + APIFrameParser::ParseResult res = parser.parse(bytes[i], &out); + REQUIRE(res == APIFrameParser::ParseResult::IDLE); + } + } +} \ No newline at end of file diff --git a/src/tests/drivers/test-mavlink.cpp b/src/tests/drivers/test-mavlink.cpp index e15a022eeb87ea186aebf8f7b93396af27976155..1cfd991812d3abdf2d09bad197a1ad0caa38b9f2 100644 --- a/src/tests/drivers/test-mavlink.cpp +++ b/src/tests/drivers/test-mavlink.cpp @@ -20,7 +20,13 @@ * THE SOFTWARE. */ +// Ignore warnings, as we don't want to change third party generated files to +// fix them +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" #include <libs/mavlink_skyward_lib/mavlink_lib/r2a/mavlink.h> +#pragma GCC diagnostic pop #include <Common.h> #include <drivers/gamma868/Gamma868.h> diff --git a/src/tests/drivers/xbee/EnergyScanData.h b/src/tests/drivers/xbee/EnergyScanData.h new file mode 100644 index 0000000000000000000000000000000000000000..68e672e27e27e0b952747a23656e35072bd197b3 --- /dev/null +++ b/src/tests/drivers/xbee/EnergyScanData.h @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <array> +#include <string> +#include <ostream> + +using std::array; +using std::string; +using std::to_string; + +struct EnergyScanData +{ + long long timestamp; + int channel_data[30]; + + EnergyScanData() = default; + + EnergyScanData(long long ts, array<int, 30> scan) + { + for(int i = 0; i < 30; i++) + { + channel_data[i] = scan[i]; + } + } + + static string header() + { + string out = "timestamp"; + for(int i = 0; i < 30; i++) + { + out += ",channel_" + to_string(i); + } + return out + "\n"; + } + + void print(std::ostream& os) const + { + os << timestamp; + + for(int i = 0; i < 30; i++) + { + os << "," << channel_data[i]; + } + + os << "\n"; + } +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/Mark.h b/src/tests/drivers/xbee/Mark.h new file mode 100644 index 0000000000000000000000000000000000000000..52b3c9be11029702d43fc312f3c85ca7a311cd64 --- /dev/null +++ b/src/tests/drivers/xbee/Mark.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <string> +#include <ostream> + +using std::string; +/** + * @brief Used to mark a specific instant in the log + */ +struct Mark +{ + long long timestamp; + unsigned int seq; + + static string header() + { + return "timestamp,seq\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << seq << "\n"; + } +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/XbeeTestData.h b/src/tests/drivers/xbee/XbeeTestData.h new file mode 100644 index 0000000000000000000000000000000000000000..3416b48e48f08ec51b1b18b050b62b3a142aa705 --- /dev/null +++ b/src/tests/drivers/xbee/XbeeTestData.h @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <ostream> +#include <array> +#include <string> + +#include "math/Stats.h" + +using std::array; +using std::string; +using std::to_string; + +struct XbeeConfig +{ + long long timestamp; + bool tx_enabled = false; + uint16_t packet_size = 256; + unsigned int send_interval = 0; + bool freq_hop = true; + bool data_rate_80k = false; + + void print() + { + printf("+++XBee configuration+++\n\n"); + printf("Tx: %s, pkt: %u B, int: %d ms\n", + tx_enabled ? "enabled" : "disabled", packet_size, send_interval); + printf("Freq hop: %s, data rate: %s \n", freq_hop ? "on" : "off", + data_rate_80k ? "80 kbps" : "10 kbps"); + } + + static string header() + { + return "timestamp,tx_enabled,packet_size,send_interval,freq_hop,data_" + "rate_80k\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << tx_enabled << "," << packet_size << "," + << send_interval << "," << freq_hop << "," << data_rate_80k << "\n"; + } +}; + +struct TxData +{ + long long timestamp = 0LL; + unsigned int packet_size = 0; + unsigned int time_since_last_send = 0; + unsigned int time_to_send = 0; + unsigned int tx_success_counter = 0; + unsigned int tx_fail_counter = 0; + + static string header() + { + return "timestamp,packet_size,time_since_last_send,time_to_send,tx_" + "success_cnt,tx_fail_cnt\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << packet_size << "," << time_since_last_send + << "," << time_to_send << "," << tx_success_counter << "," + << tx_fail_counter << "\n"; + } +}; + +struct RxData +{ + long long timestamp; + size_t pkt_size = 0; + long long last_packet_timestamp = 0; + int RSSI = 0; + unsigned int rcv_count = 0; + unsigned int packets_lost = 0; + unsigned int rcv_errors = 0; + unsigned int rcv_wrong_payload = 0; + + static string header() + { + return "timestamp,packet_size,last_packet_timestamp,RSSI,rcv_cnt,rcv_errors," + "rcv_wrong\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << pkt_size << "," << last_packet_timestamp << "," + << RSSI << "," << rcv_count << "," << rcv_errors << "," + << rcv_wrong_payload << "\n"; + } +}; + +struct EnergyScanData +{ + long long timestamp; + int channel_data[30]; + + EnergyScanData() = default; + + EnergyScanData(long long ts, array<int, 30> scan) + { + for(int i = 0; i < 30; i++) + { + channel_data[i] = scan[i]; + } + + timestamp = ts; + } + + static string header() + { + string out = "timestamp"; + for(int i = 0; i < 30; i++) + { + out += ",channel_" + to_string(i); + } + return out + "\n"; + } + + void print(std::ostream& os) const + { + os << timestamp; + + for(int i = 0; i < 30; i++) + { + os << "," << channel_data[i]; + } + + os << "\n"; + } +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/XbeeTransceiver.h b/src/tests/drivers/xbee/XbeeTransceiver.h new file mode 100644 index 0000000000000000000000000000000000000000..82000b9c9f595d813f7335e37cab611c5577e41b --- /dev/null +++ b/src/tests/drivers/xbee/XbeeTransceiver.h @@ -0,0 +1,424 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <arch/common/drivers/stm32_hardware_rng.h> +#include <miosix.h> + +#include <functional> + +#include "ActiveObject.h" +#include "XbeeTestData.h" +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/Xbee.h" +#include "logger/Logger.h" +#include "utils/testutils/ThroughputCalculator.h" + +using miosix::getTick; +using std::bind; +using std::function; + +static constexpr size_t RCV_BUF_SIZE = 256; +static constexpr uint32_t PACKET_FIRST_INT = 0xF0E1C2B3; + +/** + * @brief Repeats the provided 32 bit value in the buffer, filling it. + * + * @param buf Buffer to fill + * @param val Value to fill the buffer with + * @param buf_size size of the buffer + */ +void memset32(uint8_t* buf, uint32_t val, int buf_size) +{ + int i = 0; + while (i <= buf_size - (int)sizeof(val)) + { + memcpy(buf + i, &val, sizeof(val)); + i += sizeof(val); + } + + memcpy(buf + i, &val, buf_size % sizeof(val)); +} + +struct SendIntervalBase +{ + virtual unsigned int getInterval() const = 0; +}; + +struct ConstSendInterval : public SendIntervalBase +{ + ConstSendInterval(unsigned int interval) : interval(interval) {} + unsigned int interval; + unsigned int getInterval() const override { return interval; } +}; + +struct RandSendInterval : public SendIntervalBase +{ + RandSendInterval(unsigned int min, unsigned int max) + : min(min), diff(max - min) + { + } + unsigned int min; + unsigned int diff; + unsigned int getInterval() const override + { + return (miosix::HardwareRng::instance().get() % diff) + min; + } +}; + +class Sender : public ActiveObject +{ + friend class XbeeTransceiver; + +public: + Sender(Xbee::Xbee& xbee, Logger& logger, const SendIntervalBase& interval, + size_t packet_size, long long expected_packet_interval) + : xbee(xbee), logger(logger), dr_calc(expected_packet_interval), + interval(interval) + { + data.packet_size = packet_size; + packet_counter = 0; + } + + TxData getTxData() const { return data; } + + DataRateResult getDataRate() { return dr_calc.getResult(); } + +protected: + void run() override + { + long long loop_start_ts = getTick(); + while (!shouldStop()) + { + data.time_since_last_send = getTick() - loop_start_ts; + loop_start_ts = getTick(); + + // Create packet + memcpy(buf, &PACKET_FIRST_INT, sizeof(uint32_t)); + memset32(buf + sizeof(uint32_t), packet_counter++, + data.packet_size - sizeof(uint32_t)); + + long long send_start_ts = getTick(); + + bool result = xbee.send(buf, data.packet_size); + + data.time_to_send = getTick() - send_start_ts; + data.timestamp = send_start_ts; + + if (result) + { + ++data.tx_success_counter; + dr_calc.addPacket(data.packet_size); + } + else + { + ++data.tx_fail_counter; + } + + logger.log(data); + + miosix::Thread::sleepUntil(loop_start_ts + interval.getInterval()); + } + } + +private: + Xbee::Xbee& xbee; + Logger& logger; + ThroughputCalculator dr_calc; + TxData data; + const SendIntervalBase& interval; + + uint32_t packet_counter = 0; + + uint8_t buf[Xbee::MAX_PACKET_PAYLOAD_LENGTH]; +}; + +class Receiver : public ActiveObject +{ + friend class XbeeTransceiver; + +public: + Receiver(Xbee::Xbee& xbee, Logger& logger, + long long expected_packet_interval) + : xbee(xbee), logger(logger), dr_calc(expected_packet_interval) + { + } + + void stop() override + { + should_stop = true; + xbee.wakeReceiver(true); + ActiveObject::stop(); + } + + RxData getRxData() const { return data; } + + DataRateResult getDataRate() { return dr_calc.getResult(); } + +protected: + void run() override + { + while (!shouldStop()) + { + long long start = getTick(); + + size_t len = xbee.receive(buf, RCV_BUF_SIZE); + + data.last_packet_timestamp = getTick(); + data.timestamp = start; + + ++data.rcv_count; + + if (len > sizeof(uint32_t)) + { + data.pkt_size = len; + + // Packet payload is a repeated sequential 32 bit number + uint32_t num, strt; + memcpy(&strt, buf, sizeof(uint32_t)); + memcpy(&num, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (checkPacket(num, buf, len)) + { + dr_calc.addPacket(len); + if (last_packet_num > 0) + { + if (num > last_packet_num) + data.packets_lost += num - last_packet_num - 1; + } + last_packet_num = num; + } + else + { + TRACE("[XBeeTransceiver] Wrong packet payload! num: %u, start: 0x%08X\n", num, strt); + ++data.rcv_wrong_payload; + } + } + + logger.log(data); + } + } + +private: + bool checkPacket(uint32_t expected_num, uint8_t* packet, size_t packet_size) + { + if (memcmp(packet, &PACKET_FIRST_INT, sizeof(uint32_t)) == 0) + { + int i = sizeof(uint32_t); + while (i <= (int)packet_size - (int)sizeof(expected_num)) + { + if (memcmp(packet + i, &expected_num, sizeof(expected_num)) != + 0) + { + return false; + } + i += sizeof(expected_num); + } + memcpy(packet + i, &expected_num, + packet_size % sizeof(expected_num)); + return true; + } + return false; + } + + Xbee::Xbee& xbee; + Logger& logger; + ThroughputCalculator dr_calc; + RxData data{}; + + uint8_t buf[RCV_BUF_SIZE]; + uint32_t last_packet_num = 0; +}; + +class XbeeTransceiver +{ +public: + XbeeTransceiver(Xbee::Xbee& xbee, Logger& logger, + const SendIntervalBase& snd_interval, size_t tx_pkt_size, + long long expected_packet_interval) + : sender(new Sender(xbee, logger, snd_interval, tx_pkt_size, + expected_packet_interval)), + receiver(new Receiver(xbee, logger, expected_packet_interval)), + logger(logger) + { + using namespace std::placeholders; + xbee.setOnFrameReceivedListener( + bind(&XbeeTransceiver::handleApiFrame, this, _1)); + } + + ~XbeeTransceiver() + { + sender->stop(); + receiver->stop(); + + delete sender; + delete receiver; + } + + void disableSender() { sender_enabled = false; } + + void disableReceiver() { receiver_enabled = false; } + + void start() + { + if (sender_enabled) + { + sender->start(); + } + if (receiver_enabled) + { + receiver->start(); + } + } + + void stop() + { + sender->stop(); + receiver->stop(); + } + + void setOnFrameReceivedListener( + Xbee::Xbee::OnFrameReceivedListener frame_listener) + { + this->frame_listener = frame_listener; + } + + Sender& getSender() { return *sender; } + + Receiver& getReceiver() { return *receiver; } + +private: + void handleApiFrame(Xbee::APIFrame& frame) + { + logAPIFrame(frame); + + if (frame_listener) + { + frame_listener(frame); + } + + if (frame.frame_type == Xbee::FTYPE_AT_COMMAND_RESPONSE) + { + Xbee::ATCommandResponseFrame* at = + frame.toFrameType<Xbee::ATCommandResponseFrame>(); + + if (strncmp(at->getATCommand(), "DB", 2) == 0) + { + receiver->data.RSSI = -*at->getCommandDataPointer(); + } + + if (strncmp(at->getATCommand(), "ER", 2) == 0) + { + uint16_t errs; + memcpy(&errs, at->getCommandDataPointer(), 2); + receiver->data.rcv_errors = swapBytes16(errs); + } + } + } + + void logAPIFrame(Xbee::APIFrame& frame) + { + using namespace Xbee; + bool logged = false; + switch (frame.frame_type) + { + case FTYPE_AT_COMMAND: + { + ATCommandFrameLog dest; + logged = ATCommandFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_AT_COMMAND_RESPONSE: + { + ATCommandResponseFrameLog dest; + logged = ATCommandResponseFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_MODEM_STATUS: + { + ModemStatusFrameLog dest; + logged = ModemStatusFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_TX_REQUEST: + { + TXRequestFrameLog dest; + logged = TXRequestFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_TX_STATUS: + { + TXStatusFrameLog dest; + logged = TXStatusFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_RX_PACKET_FRAME: + { + RXPacketFrameLog dest; + logged = RXPacketFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + } + + if (!logged) + { + APIFrameLog api; + APIFrameLog::fromAPIFrame(frame, &api); + logger.log(api); + } + } + + bool sender_enabled = true; + bool receiver_enabled = true; + + Sender* sender; + Receiver* receiver; + Logger& logger; + + Xbee::Xbee::OnFrameReceivedListener frame_listener; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/ConfigScreen.h b/src/tests/drivers/xbee/gui/ConfigScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..32140ef6501e79ca7ebcc9104e0fe47a51dc9c32 --- /dev/null +++ b/src/tests/drivers/xbee/gui/ConfigScreen.h @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <cstdint> +#include <mxgui/display.h> + +#include "utils/gui/GridLayout.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" +#include "utils/gui/OptionView.h" + +#include "../XbeeTestData.h" + +struct ConfigScreen +{ + XbeeConfig config; + + enum OptSendEnableIDs : uint8_t + { + TX_ENABLED, + TX_DISABLED + }; + + enum OptFreqHopIDs : uint8_t + { + FH_ENABLED, + FH_DISABLED + }; + + enum OptDataRateIDs : uint8_t + { + DR_10K, + DR_80K + }; + + enum OptPacketSizeIDs : uint8_t + { + PKTSIZE_64, + PKTSIZE_128, + PKTSIZE_256 + }; + + enum OptSendIntervalIDs : uint8_t + { + TXINT_CONTINUOUS, + TXINT_200MS, + TXINT_250MS, + TXINT_333MS, + TXINT_500MS, + TXINT_1000MS + }; + + ConfigScreen() + { + title.setFont(mxgui::miscFixedBold); + title.setTextColor(mxgui::black); + title.setBackgroundColor(mxgui::green); + title.setAlignment(HorizAlignment::LEFT, VertAlignment::CENTER); + + tv_log_status.setFont(mxgui::miscFixedBold); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + tv_log_status.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + grid_title.setCell(&title, 0, 0); + grid_title.setCell(&tv_log_status, 0, 1); + + grid.setCell(&btn_energy, 0); + grid.setCell(&btn_start, 1); + grid.setDrawBorder(true); + + btn_start.setSelectable(true); + btn_start.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_start.setBackgroundColor(mxgui::darkGrey); + + btn_energy.setSelectable(true); + btn_energy.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_energy.setBackgroundColor(mxgui::darkGrey); + + root.addView(&grid_title, 0.6); + root.addView(&opt_send_enable, 1); + root.addView(&opt_packet_size, 1); + + root.addView(&opt_send_interval, 2); + + root.addView(&opt_freq_hop, 1); + root.addView(&opt_data_rate, 1); + root.addView(&grid, 1); + + addOptionCallbacks(); + } + + void updateLogStatus(Logger& logger) + { + if (logger.getLogNumber() >= 0) + { + string log_name = logger.getFileName(logger.getLogNumber()); + + tv_log_status.setText(log_name); + tv_log_status.setTextColor(mxgui::black); + tv_log_status.setBackgroundColor(mxgui::green); + } + else + { + tv_log_status.setText("SD ERR"); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + } + } + + VerticalLayout root{10}; + + TextView btn_start{"Start"}; + TextView btn_energy{"Energy scan"}; + +private: + void addOptionCallbacks() + { + opt_send_enable.addOnOptionChosenListener( + [&](unsigned int id) { config.tx_enabled = id == TX_ENABLED; }); + + opt_send_interval.addOnOptionChosenListener([&](unsigned int id) { + switch (id) + { + case TXINT_CONTINUOUS: + config.send_interval = 0; + break; + case TXINT_200MS: + config.send_interval = 200; + break; + case TXINT_250MS: + config.send_interval = 250; + break; + case TXINT_333MS: + config.send_interval = 333; + break; + case TXINT_500MS: + config.send_interval = 500; + break; + case TXINT_1000MS: + config.send_interval = 1000; + break; + } + }); + + opt_packet_size.addOnOptionChosenListener([&](unsigned int id) { + switch (id) + { + case PKTSIZE_64: + config.packet_size = 64; + break; + case PKTSIZE_128: + config.packet_size = 128; + break; + case PKTSIZE_256: + config.packet_size = 256; + break; + } + }); + + opt_freq_hop.addOnOptionChosenListener( + [&](unsigned int id) { config.freq_hop = id == FH_ENABLED; }); + + opt_data_rate.addOnOptionChosenListener( + [&](unsigned int id) { config.data_rate_80k = id == DR_80K; }); + } + + OptionView opt_send_enable{ + "Transmission", + {{TX_ENABLED, "Enabled"}, {TX_DISABLED, "Disabled"}}, + TX_DISABLED, + 2}; + + OptionView opt_packet_size{ + "TX Packet Size", + {{PKTSIZE_64, "64 B"}, {PKTSIZE_128, "128 B"}, {PKTSIZE_256, "256 B"}}, + PKTSIZE_256, + 3}; + + OptionView opt_send_interval{"Send Interval", + {{TXINT_CONTINUOUS, "Cont"}, + {TXINT_200MS, "200 ms"}, + {TXINT_250MS, "250 ms"}, + {TXINT_333MS, "333 ms"}, + {TXINT_500MS, "500 ms"}, + {TXINT_1000MS, "1 sec"}}, + TXINT_CONTINUOUS, + 4}; + + OptionView opt_freq_hop{ + "Frequency hopping", + {{FH_ENABLED, "Enabled"}, {FH_DISABLED, "Disabled"}}, + FH_ENABLED, + 2}; + OptionView opt_data_rate{ + "Xbee datarate", {{DR_10K, "10 kbps"}, {DR_80K, "80 kbps"}}, DR_10K, 2}; + + GridLayout grid{1, 2}; + GridLayout grid_title{1,2}; + TextView title{"Xbee Setup"}; + TextView tv_log_status{"SD ERR!"}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/EndScreen.h b/src/tests/drivers/xbee/gui/EndScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..4c19f211a6f1d55a84a72c0a52a426922545b7ff --- /dev/null +++ b/src/tests/drivers/xbee/gui/EndScreen.h @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <cstdint> +#include <mxgui/display.h> + +#include "utils/gui/GridLayout.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" +#include "utils/gui/OptionView.h" + + + +struct EndScreen +{ + EndScreen() + { + tv_title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + tv_f.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + tv_reset.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + tv_f.setSelectable(true); + tv_reset.setSelectable(true); + + root.addView(&tv_title, 1); + root.addView(&tv_f, 1); + root.addView(&tv_reset, 1); + } + + VerticalLayout root; + + TextView tv_f{"Press F to pay respects"}; + TextView tv_reset{"Or RESET to start again"}; +private: + TextView tv_title{"You killed the program, you maniac!"}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/EnergyScanScreen.h b/src/tests/drivers/xbee/gui/EnergyScanScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..a463f90672637330dbded3afb6d0c5fd946ed8e2 --- /dev/null +++ b/src/tests/drivers/xbee/gui/EnergyScanScreen.h @@ -0,0 +1,232 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <mxgui/display.h> + +#include <array> +#include <cstdint> +#include <limits> +#include <string> +#include <utility> + +#include "math/Stats.h" +#include "utils/gui/GridLayout.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" + +using std::array; +using std::to_string; + +struct EnergyScanScreen +{ + static constexpr unsigned int NUM_CHANNELS = 30; + static constexpr unsigned int NUM_CHANNEL_ROWS = (NUM_CHANNELS + 1) / 2; + static constexpr unsigned int NUM_COLS = 8; + + static constexpr float COLOR_PERCENTILE = 0.15f; + EnergyScanScreen() + { + tv_title.setFont(mxgui::miscFixedBold); + tv_title.setTextColor(mxgui::black); + tv_title.setBackgroundColor(mxgui::green); + tv_title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + tv_log_status.setFont(mxgui::miscFixedBold); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + tv_log_status.setAlignment(HorizAlignment::CENTER, + VertAlignment::CENTER); + + grid_title.setCell(&tv_title, 0, 0); + grid_title.setCell(&tv_log_status, 0, 1); + + for (unsigned int i = 0; i < NUM_COLS; i++) + { + col_titles[i] = new TextView(titles[i % (NUM_COLS / 2)]); + col_titles[i]->setTextColor(mxgui::blue); + + grid_channels.setCell(col_titles[i], 0, i); + } + + mxgui::Color pink(0xFC59); + for (unsigned int i = 0; i < NUM_CHANNELS; ++i) + { + char buf[3]; + snprintf(buf, 3, "%02d", i); + + col_names[i] = new TextView(buf); + col_names[i]->setTextColor(pink); + + col_current[i] = new TextView("-40"); + col_min[i] = new TextView("-40"); + col_max[i] = new TextView("-40"); + + grid_channels.setCell(col_names[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 0); + grid_channels.setCell(col_current[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 1); + grid_channels.setCell(col_min[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 2); + grid_channels.setCell(col_max[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 3); + } + + btn_mark.setSelectable(true); + btn_mark.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_mark.setBackgroundColor(mxgui::darkGrey); + + btn_stop.setSelectable(true); + btn_stop.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_stop.setBackgroundColor(mxgui::darkGrey); + + btn_reset.setSelectable(true); + btn_reset.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_reset.setBackgroundColor(mxgui::darkGrey); + + grid_buttons.setCell(&btn_mark, 0); + grid_buttons.setCell(&btn_reset, 1); + grid_buttons.setCell(&btn_stop, 2); + grid_buttons.setDrawBorder(true); + + root.addView(&grid_title, 0.8); + root.addView(&grid_channels, 10); + root.addView(&grid_buttons, 1); + } + + ~EnergyScanScreen() + { + for (unsigned int i = 0; i < NUM_COLS; i++) + { + delete col_titles[i]; + } + + for (unsigned int i = 0; i < NUM_CHANNELS; ++i) + { + delete col_names[i]; + delete col_current[i]; + delete col_min[i]; + delete col_max[i]; + } + } + + void updateScan(array<int, NUM_CHANNELS> scan) + { + for (unsigned int i = 0; i < NUM_CHANNELS; i++) + { + ch_stats[i].add(scan[i]); + + StatsResult r = ch_stats[i].getStats(); + + abs_max = std::max(r.maxValue, abs_max); + abs_min = std::min(r.minValue, abs_min); + } + + for (unsigned int i = 0; i < NUM_CHANNELS; i++) + { + StatsResult r = ch_stats[i].getStats(); + + col_min[i]->setText(to_string((int)r.minValue)); + setColor(col_min[i], r.minValue); + + col_current[i]->setText(to_string(scan[i])); + setColor(col_current[i], scan[i]); + + col_max[i]->setText(to_string((int)r.maxValue)); + setColor(col_max[i], r.maxValue); + } + } + + void resetStats() + { + for (unsigned int i = 0; i < NUM_CHANNELS; i++) + { + ch_stats[i].reset(); + } + + abs_max = std::numeric_limits<float>::lowest(); + abs_min = std::numeric_limits<float>::max(); + } + + void updateLogStatus(Logger& logger) + { + if (logger.getLogNumber() >= 0) + { + string log_name = logger.getFileName(logger.getLogNumber()); + + tv_log_status.setText(log_name); + tv_log_status.setTextColor(mxgui::black); + tv_log_status.setBackgroundColor(mxgui::green); + } + else + { + tv_log_status.setText("SD ERR"); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + } + } + + VerticalLayout root{5}; + + TextView btn_mark{"Mark Log (1)"}; + TextView btn_stop{"Stop"}; + TextView btn_reset{"Reset"}; + +private: + void setColor(TextView* tv, float val) + { + float delta = abs_max - abs_min; + if (val >= abs_max - delta * COLOR_PERCENTILE) + { + tv->setTextColor(mxgui::green); + } + else if (val <= abs_min + delta * COLOR_PERCENTILE) + { + tv->setTextColor(0xFDC0); // Light orange + } + else + { + tv->setTextColor(mxgui::white); + } + } + float abs_max = std::numeric_limits<float>::lowest(); + float abs_min = std::numeric_limits<float>::max(); + + GridLayout grid_channels{NUM_CHANNEL_ROWS + 1, NUM_COLS}; + GridLayout grid_title{1, 2}; + GridLayout grid_buttons{1, 3}; + + TextView tv_log_status{"SD ERR"}; + TextView tv_title{"Energy Scan"}; + + array<std::string, NUM_COLS / 2> titles = {"CH", "Curr", "Min", "Max"}; + array<TextView*, NUM_COLS> col_titles; + + array<TextView*, NUM_CHANNELS> col_names; + array<TextView*, NUM_CHANNELS> col_current; + array<TextView*, NUM_CHANNELS> col_min; + array<TextView*, NUM_CHANNELS> col_max; + + array<Stats, NUM_CHANNELS> ch_stats; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/RespectScreen.h b/src/tests/drivers/xbee/gui/RespectScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..2dbc1d6ef37302bdfa440e4b9fd40698643334d1 --- /dev/null +++ b/src/tests/drivers/xbee/gui/RespectScreen.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <cstdint> +#include <mxgui/display.h> + +#include "res/respect.h" +#include "utils/gui/ImageView.h" + +struct RespectScreen +{ + RespectScreen() + { + + } + + ImageView root{&respect}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/StatusScreen.h b/src/tests/drivers/xbee/gui/StatusScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..b9661a4047a5f53901a27e2f19780e962f48766c --- /dev/null +++ b/src/tests/drivers/xbee/gui/StatusScreen.h @@ -0,0 +1,334 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <mxgui/display.h> + +#include <cstdint> +#include <string> +#include <cstring> + +#include "utils/testutils/ThroughputCalculator.h" +#include "../XbeeTestData.h" +#include "logger/Logger.h" + +#include "utils/gui/GridLayout.h" +#include "utils/gui/OptionView.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" + +using std::to_string; + +/** + * @brief Converts tick in milliseconds to the HH:MM:SS format + */ +std::string tickToHMS(long long tick) +{ + char buf[15]; + + int h = tick / (1000 * 3600); + tick -= h * (1000 * 3600); + int m = tick / (1000 * 60); + tick -= m * (1000 * 60); + int s = tick/1000; + + snprintf(buf, 15, "%02d:%02d:%02d", h, m, s); + + return string(buf); +} + +struct StatusScreen +{ + XbeeConfig config; + + StatusScreen() + { + title.setFont(mxgui::miscFixedBold); + title.setTextColor(mxgui::black); + title.setBackgroundColor(mxgui::green); + title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + tv_log_status.setFont(mxgui::miscFixedBold); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + tv_log_status.setAlignment(HorizAlignment::CENTER, + VertAlignment::CENTER); + + grid_title.setCell(&title, 0, 0); + grid_title.setCell(&tv_log_status, 0, 1); + + grid_config.setCell(&tv_cfg_txt_tx_enabled, 0, 0); + grid_config.setCell(&tv_cfg_tx_enabled, 0, 1); + + grid_config.setCell(&tv_cfg_txt_pkt_size, 0, 2); + grid_config.setCell(&tv_cfg_pkt_size, 0, 3); + + grid_config.setCell(&tv_cfg_txt_snd_interval, 1, 0); + grid_config.setCell(&tv_cfg_snd_interval, 1, 1); + + grid_config.setCell(&tv_cfg_txt_freq_hop, 1, 2); + grid_config.setCell(&tv_cfg_freq_hop, 1, 3); + + grid_config.setCell(&tv_cfg_txt_data_rate, 2, 0); + grid_config.setCell(&tv_cfg_data_rate, 2, 1); + + tv_log_title.setTextColor(mxgui::blue); + + grid_log_status.setCell(&tv_log_title, 0, 0); + + grid_log_status.setCell(&tv_log_txt_buf_written, 1, 0); + grid_log_status.setCell(&tv_log_buf_written, 1, 1); + + grid_log_status.setCell(&tv_log_txt_buf_ttw, 1, 2); + grid_log_status.setCell(&tv_log_buf_ttw, 1, 3); + + grid_log_status.setCell(&tv_log_txt_buf_dropped, 2, 0); + grid_log_status.setCell(&tv_log_buf_dropped, 2, 1); + + grid_log_status.setCell(&tv_log_txt_buf_failed, 2, 2); + grid_log_status.setCell(&tv_log_buf_failed, 2, 3); + + tv_tx_title.setTextColor(mxgui::blue); + + grid_data.setCell(&tv_tx_title, 0, 0); + + grid_data.setCell(&tv_tx_txt_num_pkt, 1, 0); + grid_data.setCell(&tv_tx_num_pkt, 1, 1); + + grid_data.setCell(&tv_tx_txt_num_fail, 2, 0); + grid_data.setCell(&tv_tx_num_fail, 2, 1); + + grid_data.setCell(&tv_tx_txt_pps, 3, 0); + grid_data.setCell(&tv_tx_pps, 3, 1); + + grid_data.setCell(&tv_tx_txt_TTS, 4, 0); + grid_data.setCell(&tv_tx_tts, 4, 1); + + grid_data.setCell(&tv_tx_txt_last_status, 5, 0); + grid_data.setCell(&tv_tx_last_status, 5, 1); + + grid_data.setCell(&tv_tx_txt_last_err, 6, 0); + grid_data.setCell(&tv_tx_last_err, 6, 1); + + tv_rx_title.setTextColor(mxgui::blue); + grid_data.setCell(&tv_rx_title, 0, 2); + + grid_data.setCell(&tv_rx_txt_num_pkt, 1, 2); + grid_data.setCell(&tv_rx_num_pkt, 1, 3); + + grid_data.setCell(&tv_rx_txt_num_fail, 2, 2); + grid_data.setCell(&tv_rx_num_fail, 2, 3); + + grid_data.setCell(&tv_rx_txt_lost, 3, 2); + grid_data.setCell(&tv_rx_lost, 3, 3); + + grid_data.setCell(&tv_rx_txt_RSSI, 4, 2); + grid_data.setCell(&tv_rx_RSSI, 4, 3); + + grid_data.setCell(&tv_rx_txt_data_rate, 5, 2); + grid_data.setCell(&tv_rx_data_rate, 5, 3); + + grid_data.setCell(&tv_rx_txt_pps, 6, 2); + grid_data.setCell(&tv_rx_pps, 6, 3); + + grid_data.setCell(&tv_rx_txt_time_since_last_rx, 7, 2); + grid_data.setCell(&tv_rx_time_since_last_rx, 7, 3); + + btn_mark.setSelectable(true); + btn_mark.setAlignment(HorizAlignment::CENTER, + VertAlignment::CENTER); + btn_mark.setBackgroundColor(mxgui::darkGrey); + + btn_stop.setSelectable(true); + btn_stop.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_stop.setBackgroundColor(mxgui::darkGrey); + + grid_buttons.setCell(&btn_mark, 0); + grid_buttons.setCell(&btn_stop, 1); + grid_buttons.setDrawBorder(true); + + root.addView(&grid_title, 0.8); + root.addView(&grid_config, 1.5); + root.addView(&grid_log_status, 1.5); + root.addView(&grid_data, 5); + root.addView(&grid_buttons, 1); + } + + void updateConfig(XbeeConfig cfg) + { + // Update GUI with selected config values + tv_cfg_tx_enabled.setText(cfg.tx_enabled ? "Enabled" : "Disabled"); + tv_cfg_pkt_size.setText(std::to_string(cfg.packet_size)); + tv_cfg_snd_interval.setText(cfg.send_interval == 0 + ? "Cont" + : std::to_string(cfg.send_interval)); + + tv_cfg_freq_hop.setText(cfg.freq_hop ? "Enabled" : "Disabled"); + tv_cfg_data_rate.setText(cfg.data_rate_80k ? "80 kbps" : "10 kbps"); + } + + void updateLogStatus(Logger& logger) + { + LogStats stats = logger.getLogStats(); + + if (logger.getLogNumber() >= 0) + { + string log_name = logger.getFileName(logger.getLogNumber()); + + tv_log_status.setText(log_name); + tv_log_status.setTextColor(mxgui::black); + tv_log_status.setBackgroundColor(mxgui::green); + } + else + { + tv_log_status.setText("SD ERR"); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + } + + tv_log_buf_dropped.setText(to_string(stats.statDroppedSamples)); + + if (stats.statDroppedSamples > 0) + { + tv_log_buf_dropped.setBackgroundColor(mxgui::red); + } + + tv_log_buf_failed.setText(to_string(stats.statWriteFailed) + " (" + + to_string(stats.statWriteError) + ")"); + + if (stats.statWriteError != 0) + { + tv_log_buf_failed.setBackgroundColor(mxgui::red); + } + + tv_log_buf_written.setText(to_string(stats.statBufferWritten)); + tv_log_buf_ttw.setText(to_string(stats.statWriteTime) + " ms"); + } + + void updateXbeeStatus(DataRateResult res_rcv, DataRateResult res_snd, + TxData txd, RxData rxd, Xbee::XbeeStatus xbee_status) + { + char str_buf[30]; + + tv_tx_num_pkt.setText( + to_string(txd.tx_success_counter + txd.tx_fail_counter)); + tv_tx_num_fail.setText(to_string(txd.tx_fail_counter)); + + snprintf(str_buf, 30, "%.1f pkt/s", res_snd.packets_per_second); + + tv_tx_pps.setText(str_buf); + tv_tx_tts.setText(to_string(txd.time_to_send) + " ms"); + + tv_tx_last_status.setText(to_string(xbee_status.last_tx_status)); + tv_tx_last_err.setText(to_string(xbee_status.last_tx_status_error)); + + tv_rx_num_pkt.setText(to_string(rxd.rcv_count)); + tv_rx_num_fail.setText(to_string(rxd.rcv_errors)); + // tv_rx_num_fail.setText(to_string(int_counter) + " " + + // to_string(GpioATTN::value())); + tv_rx_lost.setText(to_string(rxd.packets_lost)); + + tv_rx_RSSI.setText(to_string(rxd.RSSI) + " dB"); + + snprintf(str_buf, 30, "%.0f B/s", res_rcv.data_rate); + tv_rx_data_rate.setText(str_buf); + + snprintf(str_buf, 30, "%.1f pkt/s", res_rcv.packets_per_second); + tv_rx_pps.setText(str_buf); + + tv_rx_time_since_last_rx.setText(tickToHMS(miosix::getTick() - rxd.last_packet_timestamp)); + } + + VerticalLayout root{10}; + + TextView tv_cfg_tx_enabled{"Disabled"}; + TextView tv_cfg_pkt_size{"256 B"}; + TextView tv_cfg_snd_interval{"Cont"}; + TextView tv_cfg_freq_hop{"Enabled"}; + TextView tv_cfg_data_rate{"10 kbps"}; + + TextView tv_log_status{"SD ERR"}; + + TextView tv_log_buf_dropped{"0"}; + TextView tv_log_buf_failed{"0 (0)"}; + TextView tv_log_buf_written{"0"}; + TextView tv_log_buf_ttw{"0 ms"}; + + TextView tv_tx_num_pkt{"0"}; + TextView tv_tx_num_fail{"0"}; + TextView tv_tx_pps{"0 pkt/s"}; + TextView tv_tx_tts{"- ms"}; + TextView tv_tx_last_status{"0"}; + TextView tv_tx_last_err{"0"}; + + TextView tv_rx_num_pkt{"0"}; + TextView tv_rx_num_fail{"0"}; + TextView tv_rx_lost{"0"}; + TextView tv_rx_RSSI{"-40 dB"}; + TextView tv_rx_data_rate{"0 B/s"}; + TextView tv_rx_pps{"0 pkt/s"}; + TextView tv_rx_time_since_last_rx{"00:00:00"}; + + TextView btn_mark{"Mark Log (1)"}; + TextView btn_stop{"Stop"}; + +private: + TextView title{"Xbee Status"}; + GridLayout grid_title{1, 2}; + GridLayout grid_config{3, 4}; + GridLayout grid_buttons{1, 2}; + GridLayout grid_log_status{3, 4}; + GridLayout grid_data{8, 4}; + + TextView tv_cfg_txt_tx_enabled{"TX"}; + TextView tv_cfg_txt_pkt_size{"Pkt size"}; + TextView tv_cfg_txt_snd_interval{"Interv"}; + TextView tv_cfg_txt_freq_hop{"Freq hop"}; + TextView tv_cfg_txt_data_rate{"Data rate"}; + + TextView tv_log_title{"LOG"}; + TextView tv_log_txt_buf_dropped{"Buf drops"}; + TextView tv_log_txt_buf_failed{"Wrt fails"}; + TextView tv_log_txt_buf_written{"Wrt succ"}; + TextView tv_log_txt_buf_ttw{"TTW"}; + + TextView tv_tx_title{"TX"}; + + TextView tv_tx_txt_num_pkt{"Sent"}; + TextView tv_tx_txt_num_fail{"Fails"}; + TextView tv_tx_txt_pps{"PPS"}; + TextView tv_tx_txt_TTS{"TTS"}; + TextView tv_tx_txt_last_status{"Status"}; + TextView tv_tx_txt_last_err{"Last err"}; + + TextView tv_rx_title{"RX"}; + + TextView tv_rx_txt_num_pkt{"Recv"}; + TextView tv_rx_txt_num_fail{"Fails"}; + TextView tv_rx_txt_lost{"Lost"}; + TextView tv_rx_txt_RSSI{"RSSI"}; + TextView tv_rx_txt_data_rate{"DR"}; + TextView tv_rx_txt_pps{"PPS"}; + TextView tv_rx_txt_time_since_last_rx{"No RX dt"}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/XbeeGui.h b/src/tests/drivers/xbee/gui/XbeeGui.h new file mode 100644 index 0000000000000000000000000000000000000000..5e5062f2aadf584a7ca8a86ef2fd0f941489f419 --- /dev/null +++ b/src/tests/drivers/xbee/gui/XbeeGui.h @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <mxgui/display.h> + +#include <cstdint> +#include <functional> + +#include "ConfigScreen.h" +#include "StatusScreen.h" +#include "EnergyScanScreen.h" +#include "EndScreen.h" +#include "RespectScreen.h" +#include "utils/gui/ScreenManager.h" + +class XbeeGUI +{ +public: + enum Screens : uint8_t + { + SCREEN_CONFIG, + SCREEN_STATUS, + SCREEN_ENERGYSCAN, + SCREEN_END, + SCREEN_RESPECT + }; + + XbeeGUI() + : screen_manager(mxgui::DisplayManager::instance(), 4) + { + screen_manager.addScreen(SCREEN_CONFIG, &screen_config.root); + screen_manager.addScreen(SCREEN_STATUS, &screen_status.root); + screen_manager.addScreen(SCREEN_ENERGYSCAN, &screen_energy.root); + screen_manager.addScreen(SCREEN_END, &screen_end.root); + screen_manager.addScreen(SCREEN_RESPECT, &screen_respect.root); + + screen_manager.start(); + } + + ~XbeeGUI() + { + screen_manager.stop(); + } + + ScreenManager screen_manager; + + ConfigScreen screen_config{}; + StatusScreen screen_status{}; + EnergyScanScreen screen_energy{}; + EndScreen screen_end{}; + RespectScreen screen_respect{}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/res/respect.cpp b/src/tests/drivers/xbee/gui/res/respect.cpp new file mode 100644 index 0000000000000000000000000000000000000000..90e91dd6a0ff8818fe56eac3b26a2b3fc83857dc --- /dev/null +++ b/src/tests/drivers/xbee/gui/res/respect.cpp @@ -0,0 +1,9614 @@ + +//This file has been automatcally generated by pngconverter utility +//Please do not edit +#include "respect.h" + +using namespace mxgui; + +static const short int height=320; +static const short int width =240; + +static const unsigned short pixelData[]={ + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,6371,19049, + 29614,35921,38066,38034,35953,29614,21130,10597, + 2145,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,10597,33808,50712,61277,65503, + 65535,65535,65535,65535,65535,65535,65535,65535, + 54970,40147,21130,4258,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,10597,42260,63422,65535,65535,65535,65535, + 65503,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,57051,31727,6371,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 29582,61277,65503,65535,65503,59164,42260,31727, + 23243,19017,16936,21162,27501,35953,48631,61277, + 63422,65535,65535,65535,65535,61277,35921,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,4226,40179, + 65535,65535,61277,38034,14823,32,0,0, + 0,0,0,32,0,0,32,2145, + 19017,33840,57083,65535,65535,65535,65535,52857, + 14823,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4226,44405,65503, + 63390,38034,6371,0,32,4226,14823,23275, + 27469,25388,21162,16904,10565,4258,32,32, + 0,0,2145,25388,57051,65535,65535,65535, + 61309,19049,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,42260,65503,57083, + 19017,2113,32,4258,33840,57083,65503,65503, + 65535,65535,65503,65503,63422,61277,50744,38066, + 23243,6339,32,32,4258,38066,65535,65535, + 65535,59196,10565,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,31727,65503,52857,12678, + 0,0,14791,52857,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65503,63390,44373,19049,2113,32,27501,63390, + 65535,65535,50744,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,14823,63390,57051,8452,0, + 0,12710,59164,65535,65535,63390,63390,63422, + 65535,65535,65535,65535,65535,65503,65535,65535, + 65535,65503,65503,65503,48599,10565,0,27501, + 65503,65535,65503,25388,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,2145,54938,63422,16936,0,0, + 4258,54938,65535,63390,31695,12678,6371,12678, + 14823,19017,23243,27469,29582,31727,35953,40179, + 52825,63390,65535,65535,65503,57083,12678,32, + 40147,65535,65535,52857,2113,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,31727,65535,31695,32,0,0, + 35953,65535,63390,21162,0,0,0,0, + 0,0,0,32,0,0,0,32, + 0,12678,38066,63422,65535,65535,52825,4258, + 6371,59164,65535,65503,19049,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,10565,61309,54938,4226,0,0,14791, + 63422,65503,31695,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,19017,59196,65535,65535,27469, + 0,29582,65535,65535,42260,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,0, + 32,42292,65535,23243,0,0,2145,48599, + 65535,44405,2113,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,16904,61309,65535,52857, + 2113,6371,61277,65535,57051,2145,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 12710,63390,52857,2113,32,0,19049,63422, + 57083,8484,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,27501,65535,63422, + 12710,32,46486,65535,63422,12710,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,32, + 42260,65535,29582,0,0,2113,50712,65535, + 27501,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,50744,65503, + 33808,0,21130,65535,65535,25356,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,6371, + 65503,61277,8452,0,0,16936,65535,54970, + 4226,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,21130,65503, + 50712,0,6371,61309,65535,40179,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,33808, + 65535,44373,32,0,0,44405,65503,29582, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,57083, + 61309,4258,32,52857,65535,54970,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2145,54970, + 65535,23275,0,0,12678,63422,61277,8452, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,42292, + 65535,16904,0,42292,65535,59196,4258,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16936,65535, + 61277,6339,0,0,35953,65535,52825,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,27469, + 65535,27469,32,29582,65535,63422,12678,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,42260,65535, + 50712,0,0,6339,57083,65535,38034,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,14823, + 65535,31727,0,23243,65535,65535,19017,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,4258,59164,65535, + 33808,0,32,19049,65503,65535,29582,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,32,0,8484, + 63422,35953,0,16936,65535,65535,25388,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,16936,65503,65503, + 14823,0,0,40179,65535,65535,25388,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4258, + 57083,35921,0,12710,65535,65535,31695,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,35921,65535,59196, + 4226,0,4226,57083,65535,65535,27469,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2113, + 52857,31695,0,12678,65535,65535,38066,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,52857,65535,48599, + 0,0,12710,65535,65535,65535,33808,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,8452, + 61309,27501,0,10565,65535,65535,44373,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,8484,63390,65535,31727, + 0,32,31727,65535,65535,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 63390,21162,0,12678,65535,65503,44405,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,23275,65503,65503,19017, + 0,32,48631,65535,65535,65535,50712,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 63390,16936,0,14791,65535,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,40147,65535,63390,8452, + 0,4226,59196,65535,65535,65535,57083,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 61309,10565,0,16904,65535,65535,42292,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,52857,65535,57083,2113, + 32,16904,65535,65535,65535,65535,59196,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 61309,6339,0,19017,65503,65535,42260,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6339,63390,65535,46486,32, + 0,29582,65535,65535,65535,65535,61309,2145, + 0,0,0,0,0,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,19049, + 59196,2145,0,25356,65535,65535,38066,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,14823,65503,65535,35953,0, + 0,42260,65535,65535,65535,65535,61309,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,29614, + 57051,2113,0,31695,65535,65535,33840,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,29582,65535,65535,25356,0, + 2113,54938,65535,65535,65503,65535,63390,8484, + 32,0,2113,4226,10597,19017,27469,31695, + 31695,25356,14823,8484,2113,32,0,32, + 0,0,0,0,0,0,2113,46486, + 50744,32,0,40147,65535,65535,27469,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,40147,65535,65503,16936,0, + 4258,61309,65535,65535,65535,65535,65503,23243, + 2145,16904,40147,59164,65503,65535,65535,65535, + 65535,65535,65503,63422,52857,31727,8484,2145, + 0,0,0,0,32,0,4258,57083, + 48599,0,32,46486,65535,65503,21162,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,48599,65535,63390,10565,0, + 10597,65503,65535,65535,65535,65535,65503,63422, + 57051,65503,65535,65535,65535,65535,65503,65535, + 65535,65535,65535,65535,65535,65535,63390,44373, + 16904,32,0,0,0,32,23275,65535, + 42292,32,2113,52825,65535,65503,16936,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,2145,54938,65535,61309,8452,0, + 21162,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,59164,48631,35921, + 29582,29582,38066,50712,59164,65535,65535,65535, + 63422,44405,12678,32,0,4258,54938,65535, + 35953,0,4226,54970,65535,63390,12678,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,61277,65535,59196,4258,0, + 31727,63390,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,54970,23275,4258,0,32, + 0,0,32,32,6371,27469,54938,65535, + 65535,65503,61277,31727,10565,42260,65535,65535, + 29582,0,6371,61277,65535,59196,4258,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,8452,63390,65535,59164,2145,0, + 21130,48599,59164,63422,65535,65535,65535,65535, + 65535,65503,65535,52825,40179,33808,23275,14791, + 10565,6339,2113,0,0,0,4226,35953, + 65535,65535,65535,65535,63390,65535,65535,65535, + 23275,0,8484,63422,65535,57083,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,10565,65503,65535,52825,32,0, + 32,32,6339,12710,31695,46486,59196,63422, + 65503,65535,65535,65535,65535,65535,65535,65535, + 63422,59196,54970,48599,40147,29582,19017,14823, + 59196,65535,65535,65503,65535,65535,65535,65503, + 16936,0,14791,65535,65535,46486,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,12678,65535,65535,50744,32,0, + 32,0,0,32,0,0,4226,12678, + 25356,38066,50744,59196,65503,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 65503,65535,65535,65535,65535,65535,65535,63390, + 10565,32,23275,65535,65535,38066,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,14823,65535,65535,48631,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,4258,12710,23275,38066,48631, + 57083,63390,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,63390,42292, + 32,0,31727,65535,65535,25388,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,16936,65535,65535,46518,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 4226,8484,16936,29582,42292,52857,61309,63422, + 65503,65503,65535,65535,65503,59164,31727,2145, + 0,32,38066,65535,65535,16936,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,16936,65535,65535,46486,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,12678, + 21130,27501,29614,25388,16904,2145,0,2113, + 0,0,48599,65535,63390,8484,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,16936,65535,65535,44373,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,32,0, + 0,32,50712,65535,61277,6371,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,16936,65535,65535,42260,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,0, + 0,2145,52857,65535,57083,2145,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,16936,65535,65535,40179,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,32, + 0,0,0,32,0,0,0,0, + 0,4226,57051,65535,54970,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,12710,65535,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,4258,59196,65503,48599,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,10597,65503,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 0,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,6371,61277,65535,48599,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,8484,63422,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 29582,27501,14791,6339,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,8452,63390,65535,48599,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,6371,63390,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 21162,59196,65535,61277,52825,42260,31695,29582, + 21162,19017,14791,12678,8484,6339,2145,2145, + 2145,2113,32,32,0,0,0,0, + 32,10565,63422,65535,48599,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4226,59196,65535,44373,32,0, + 0,0,0,0,0,0,0,0, + 32,12710,52857,65503,65535,65535,65535,65535, + 65535,65535,65503,65503,65535,65503,65503,61277, + 59164,54970,54938,46518,16936,0,0,32, + 0,12678,65535,65535,46518,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,2145,54970,65535,44373,32,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,50744,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 63422,63390,54970,42292,14823,0,0,0, + 0,14791,65535,65535,46518,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,48631,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 0,0,21130,54970,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65503,59164, + 29582,10597,2113,32,0,0,0,32, + 0,19017,65535,65535,46518,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,44405,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 16904,46518,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 38066,8452,0,0,0,0,0,0, + 0,21162,65535,65503,46518,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,32,2145, + 52857,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,57051,21130,0,0,0,0,0, + 0,23275,65535,65535,44405,32,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,0,32, + 6339,31695,38066,40179,40147,40179,42292,44373, + 46486,46518,48631,50744,54938,57051,59164,59196, + 65503,65535,61309,35953,0,0,0,0, + 0,25388,65535,65535,44405,32,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,32, + 0,0,32,32,32,2113,2113,2113, + 2113,4258,8452,6339,0,0,0,0, + 0,27501,65535,65535,44373,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,0,0,2113,4226,6339, + 6339,6371,6371,6371,6371,6371,8452,6371, + 4258,0,0,0,0,0,0,0, + 0,29614,65535,65535,44373,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,46518,65535,40179,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,44373,61277, + 63390,61309,61277,61309,61309,61277,63390,54970, + 16904,0,0,0,0,0,0,0, + 0,33808,65535,65535,42292,2113,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,52825,65535,38034,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,23243, + 38066,48599,54938,54970,57051,48631,31727,6371, + 0,0,0,0,0,0,0,0, + 0,33840,65535,65535,42260,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,6339,61277,65535,33840,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,2113,2113,32,0,0,0, + 0,0,0,32,0,0,0,0, + 0,38034,65535,65535,40147,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,32,12710,65503,65535,31695,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,0,0,0,0,0, + 0,38066,65535,65503,38066,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,31695,65535,65535,27469,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,42260,65535,65535,38066,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,52825,65535,65535,19049,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,44373,65535,65535,38034,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,10597,65535,65535,63390,10565,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,46486,65535,65535,33840,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,33840,65535,65535,61277,4226,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,48599,65535,65535,31727,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2145,52857,65535,65535,50744,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,50744,65535,65535,27501,32,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 10565,63422,65535,65535,35921,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,52825,65535,65535,25356,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 29582,65535,65535,65535,19049,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2113,54938,65535,65503,19017,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 44373,65535,65535,63390,10565,0,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2113,57051,65535,65503,14823,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,4258, + 57083,65535,65535,59164,2113,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2145,59196,65535,63390,6371,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,12678, + 63422,65535,65535,46486,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 4226,59196,65535,61277,4226,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,23275, + 65535,65535,65535,31695,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 6339,61309,65535,54938,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,38066, + 65535,65535,65503,19049,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 8484,63390,65535,48631,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,2113,52857, + 65503,65535,63390,8452,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 10565,65503,65535,40179,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,61309, + 65535,65535,57083,4226,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 14791,65535,65535,27501,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16936,65535, + 65535,65535,46518,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 21162,65535,65535,16936,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,29582,65535, + 65535,65535,35953,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 33808,65535,63422,12678,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2113,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,46486,65535, + 65535,65535,25356,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,0, + 42292,65535,61277,6371,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,52857,65535, + 65535,65535,16936,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,2113, + 50744,65535,59164,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4226,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,6339,61277,65535, + 65535,63390,8484,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4258, + 57083,65535,54970,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,8452,57051,42292,14823,6371, + 4258,2145,0,0,0,0,0,0, + 0,0,0,0,0,8484,63422,65535, + 65535,61277,6339,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,6371, + 61309,65535,52825,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,33808,65535,65535,65535,61309, + 61277,59164,14823,0,0,0,0,0, + 0,0,0,0,0,19049,65535,65535, + 65535,57083,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,12678, + 65535,65535,44373,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,6371,59164,65535,65535,65535,65535, + 65535,65535,59196,10565,0,0,0,32, + 0,0,0,0,0,27469,65535,65535, + 65535,52825,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,21130, + 65535,65535,42292,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,38066,65535,65535,65535,65535,59196, + 54938,65503,65535,52857,6371,0,0,0, + 0,0,0,0,0,38034,65535,65535, + 65535,44373,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,33808, + 65535,65535,40179,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,27501,65503,65535,65503,65535,54938,10565, + 6339,54938,65535,65535,46518,4226,0,0, + 0,0,0,0,0,44405,65535,65535, + 65535,38066,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,42260, + 65535,65535,38066,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,0,0,0,2145, + 29614,63390,65535,65535,65535,65503,25356,32, + 0,21130,65535,65535,65503,35921,0,0, + 0,0,0,0,32,50712,65535,65535, + 65535,25388,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,50712, + 65535,65535,38034,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,44405, + 65535,61277,57083,65503,65535,61309,6371,0, + 0,8452,63390,65535,65535,63390,16904,0, + 0,0,0,0,2113,54970,65535,65535, + 65535,21162,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2113,52857, + 65535,65535,35953,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,32,31695,61277,65503, + 44373,6371,10597,65503,65535,46486,32,0, + 0,32,59164,65535,65535,65535,48599,2145, + 0,0,0,0,4258,61277,65535,65535, + 63422,14791,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,59164, + 65535,65535,33840,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,32,4258,44405,65535,65503,40147, + 2145,32,27469,65535,65503,19049,0,0, + 0,0,50712,65535,65535,65503,63422,12710, + 0,0,0,0,8452,63390,65535,65535, + 63390,10565,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,61309, + 65535,65535,31727,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,48631,65535,65535,38066,32, + 32,10597,61309,65535,46518,32,0,32, + 0,32,38066,65535,65535,65535,65535,33808, + 32,0,0,0,12710,65535,65535,65535, + 61277,8484,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6371,63390, + 65535,65535,31695,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,44405,65535,65503,44405,32,0, + 32,46486,65535,61277,10565,0,32,0, + 0,0,31695,65535,65535,65535,65535,50744, + 32,0,0,0,19017,65535,65535,65535, + 59196,4226,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6371,61309, + 65535,65535,31695,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,29582,65535,65535,54970,6371,0,0, + 16904,63422,65503,27469,0,0,0,0, + 0,0,27469,65535,65535,65535,65535,61277, + 6339,0,0,0,23275,65535,65535,65535, + 54970,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6339,61277, + 65535,65535,31695,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 10597,63390,65535,63390,14823,0,32,6339, + 54970,65535,44373,32,0,32,0,0, + 0,0,27469,65535,65535,65535,65535,65535, + 14791,32,0,0,27501,65535,65535,65535, + 50712,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,2145,4226,8452,10565,8484,4258, + 2145,32,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,59196, + 65535,65535,29614,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2113, + 46518,65535,65535,42260,32,0,8452,48599, + 65535,52857,4258,0,0,0,0,32, + 32,0,42260,65535,65535,65535,65535,65535, + 31727,0,0,0,35953,65535,65535,65535, + 42292,32,0,0,0,0,0,0, + 0,0,0,0,0,32,32,6371, + 27469,44373,54970,63390,63422,65503,65503,63422, + 59164,50744,40179,29582,8452,32,0,0, + 0,0,0,0,0,0,4226,57083, + 65535,65535,29582,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,32,21162, + 63422,65535,61277,10565,0,12678,52857,65535, + 61277,14791,0,0,0,0,0,0, + 2113,21162,63422,65535,65535,65535,65535,65535, + 46518,32,0,0,42292,65535,65535,65535, + 38034,0,0,0,0,0,0,0, + 0,0,0,32,0,6339,38034,61309, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,63390,40147,8484,0, + 0,0,0,0,0,0,2113,54970, + 65535,65535,29582,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,2145,54970, + 65535,65535,31695,0,19049,59196,65535,63390, + 21162,32,0,0,0,0,4258,21162, + 44405,63390,65535,65535,65535,65535,65535,65535, + 54938,2145,0,32,50744,65535,65535,65535, + 29582,0,0,0,0,0,0,0, + 0,0,32,32,21162,59164,65503,65535, + 65535,63390,61309,59164,52857,54970,57083,57083, + 63390,65503,65503,65503,65535,65535,59164,14823, + 0,0,0,0,0,0,32,52857, + 65535,65535,29582,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,31695,65535, + 65535,61309,23275,33840,63422,65535,61309,23275, + 32,0,0,0,8484,38066,61277,65503, + 65535,65535,65535,65535,65535,65535,65535,65535, + 61309,10565,32,2145,57051,65535,65535,65535, + 23275,0,32,0,0,0,0,0, + 0,32,32,27501,61277,52825,35953,25356, + 16904,6371,2113,2113,2113,2113,32,0, + 6339,12710,23275,35921,52825,63422,65503,61277, + 12710,0,0,0,32,0,0,50744, + 65535,65535,29582,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,10565,63390,65503, + 65535,65535,65535,65535,65535,59164,16936,0, + 0,0,4258,29582,61309,65503,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,23275,0,4258,61277,65535,65535,65503, + 19017,0,32,0,0,0,0,0, + 0,0,6371,21162,4258,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,8484,33808,52857, + 44405,2113,0,0,0,0,32,48631, + 65535,65535,35921,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,46486,65535,65535, + 65535,65535,65535,65535,50744,10565,0,0, + 0,21162,54970,65503,65503,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,48599,2145,21162,63422,65535,65535,65535, + 14791,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,32, + 16936,4258,32,32,0,0,0,48631, + 65535,65535,42260,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,0,2113,19049,63422,65535,65535, + 65535,65535,65535,46486,4226,0,32,12710, + 46518,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,42260,61277,65535,65535,65535,63422, + 8452,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,0,0,0,0,48631, + 65535,65535,50744,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,4258,57051,65535,65535,65503, + 65535,65535,65535,16904,32,10565,40147,65535, + 65535,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,65535,65535,65503,65535,65535,61277, + 6371,0,0,32,0,0,0,0, + 0,0,0,0,32,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,0,0,0,0,48631, + 65503,65535,59196,2145,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,33808,65535,65535,65535,65535, + 65535,65535,65535,44405,40179,61277,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,57051, + 2145,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,50744, + 65535,65535,65503,16936,32,0,0,0, + 0,0,0,32,0,0,0,0, + 0,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,10565,61277,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,65503,52857,38066,27469,21130,14791, + 12678,16904,23243,29582,42260,54970,65503,65535, + 65535,65535,65535,65535,65535,65535,65535,52825, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,52825, + 65535,65535,65535,40147,32,0,0,0, + 0,32,16936,35921,38034,31695,21130,10565, + 2145,32,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,44405,65535,65535,65535,65535,65535, + 65535,65535,65503,65535,65535,65535,65535,65535, + 61309,42292,14791,4258,8452,6339,2113,0, + 32,0,0,0,0,32,16904,42292, + 63422,65535,65535,65535,65535,65535,65535,46486, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,2113,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2113,52857, + 65535,65535,65535,59164,10565,8484,12710,19049, + 27469,40179,65535,65535,65535,65535,65535,63422, + 59196,46486,29614,16936,8484,4226,32,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,21130,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65503,48631, + 12710,2145,25388,57051,63422,61309,52857,38034, + 19017,4258,0,32,0,0,0,2113, + 10597,46518,65535,65535,65535,65535,65535,42292, + 0,0,0,0,0,0,0,0, + 0,0,0,0,29614,54970,50744,46486, + 38034,35921,31727,29614,27501,27469,23275,23243, + 19049,16904,10565,6371,0,0,0,0, + 0,0,0,0,0,0,2145,54938, + 65535,65535,65535,65535,63390,63422,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,65535,65535,63390,61277,54938,48599, + 33808,19017,2145,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 4258,54970,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,63422,65535,63390,33808,32, + 32,8452,46518,57051,63390,65535,65535,65503, + 65535,61277,42292,23243,4258,32,0,32, + 0,4258,46486,65535,65535,65535,65535,35953, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,8452,16936,23275, + 29614,33840,42260,46486,50744,54938,59164,61309, + 63422,63422,63390,63390,54970,42260,23275,10597, + 32,0,0,0,0,0,4226,59164, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65503,65535,65535, + 65535,65503,57083,31727,8452,32,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 35921,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,61309,19049,32,0, + 0,0,2145,12678,59196,65535,65535,65535, + 65535,65535,65535,65503,54970,16904,0,0, + 0,0,8452,57083,65535,65535,65535,29614, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,32,2113,2145,4258, + 10597,16904,25356,29614,40147,44373,50712,54938, + 38034,2113,0,0,0,0,6339,59196, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,63422,61277,59164,61277, + 61277,63422,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,61277,25388,32,0, + 32,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,12678, + 61309,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,59196,14823,0,32,0, + 0,14791,40147,61277,65535,65535,65535,65535, + 65535,65535,65535,52857,16936,2113,32,0, + 32,0,0,27501,65535,65535,65535,23275, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 0,0,0,0,0,0,32,2145, + 4226,0,0,0,0,0,8452,61309, + 65535,65535,65535,65535,65535,65535,65535,63390, + 50712,38034,21130,16904,16904,16904,12678,6371, + 4258,10565,19017,31695,44405,59164,65503,65535, + 65535,65535,65535,65535,65535,63422,44373,4258, + 0,32,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2113,48631, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,63390,16936,0,0,0,4258, + 42292,63422,65535,65503,65535,65535,65503,65535, + 65535,65535,65535,65503,63390,50712,25388,4258, + 0,0,32,8484,63390,65535,65535,21162, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,32,32,0,0,8484,63390, + 65535,65535,65535,65535,65535,59196,33840,8452, + 2113,2145,42292,61277,63422,65503,63390,61309, + 54938,38034,23275,10565,2113,2145,12678,33808, + 48599,61277,65535,65535,65535,65535,65535,50744, + 8484,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,27501,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,63390,21130,0,0,0,0,35953, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,54938, + 6371,0,0,2113,48631,65535,65535,16936, + 0,0,0,0,0,0,0,0, + 0,32,0,0,0,0,32,0, + 0,32,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10597,65503, + 65535,65535,65535,65535,61309,16904,32,0, + 0,0,14823,54938,65535,65535,65535,65535, + 65503,65503,65503,65503,52857,27501,6371,32, + 0,6371,35953,61309,65535,65535,65535,65535, + 52857,6371,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,61277,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,31695,0,0,32,0,0,19049, + 25356,25356,25356,25356,25356,25356,25356,23275, + 23275,23275,23275,25356,23275,23243,23275,23275, + 10597,0,0,0,38066,65535,65535,16904, + 0,0,0,0,0,0,0,0, + 6339,42292,35921,14791,2145,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,14791,65503, + 65535,65535,65535,65535,31695,0,0,0, + 0,0,0,4226,19017,42292,61309,65535, + 65535,65535,65535,65535,65535,65535,61309,46486, + 23243,4226,0,8452,44373,65503,65535,65535, + 65535,52857,8452,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,42292,65535,65535, + 65535,65535,65535,65503,65535,65535,65535,65535, + 46518,32,0,0,0,0,0,0, + 0,0,0,0,0,0,32,0, + 0,0,0,32,0,0,0,0, + 0,0,0,0,25356,65535,65535,14791, + 0,0,0,0,0,0,0,0, + 0,31695,65503,63422,59164,38034,16936,6339, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16936,65535, + 65535,65535,65535,59164,4226,0,0,32, + 0,32,2145,6371,8452,14791,33808,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65503,61277,35921,12710,2145,23243,61277,65535, + 65535,65535,50744,4258,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,21130,65535,65535,65535, + 65535,65535,61277,31727,65535,65535,65535,65535, + 12678,0,0,0,0,0,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,0, + 32,0,0,0,14791,65535,65535,12710, + 0,0,0,0,0,0,0,0, + 0,32,33808,65503,65535,65535,65503,61277, + 42292,19049,4226,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,19049,65535, + 65535,65535,65535,38066,0,0,0,0, + 10597,33808,50744,59196,63422,63422,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,63422, + 61277,63390,63390,61309,21130,0,16904,59164, + 65535,65535,63422,44373,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6371,57083,65503,65535,65535, + 65535,63422,27469,19017,65535,65503,65535,42260, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,6339,63390,65503,12678, + 0,0,0,0,0,0,0,0, + 0,0,2113,35921,65503,65535,65535,65535, + 65535,65503,59164,48599,27501,12710,4258,0, + 0,0,0,0,0,32,0,0, + 0,4226,10565,32,0,0,23243,65535, + 65535,65535,63422,16936,0,0,0,0, + 0,0,2145,10565,19049,33808,48599,54970, + 63422,65535,65535,65535,65535,65535,65535,65503, + 40179,12678,6371,8452,2145,0,32,14823, + 57083,65535,65535,63422,21130,0,0,0, + 0,0,0,0,0,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,40179,65535,65535,65535,65535, + 65535,44405,2113,21162,65535,65535,63422,12710, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,57051,65503,10597, + 0,0,0,0,0,0,0,0, + 0,0,32,2113,27501,61277,65535,65535, + 65535,65535,65535,65503,65535,65503,59196,48631, + 38034,31695,27469,27501,29582,29582,31695,31727, + 44373,44373,8452,0,0,0,23275,65535, + 65535,65535,59196,4226,0,0,0,0, + 0,0,0,0,32,0,32,2145, + 8452,23243,40147,48599,57083,63422,65535,65535, + 65535,61277,33808,10597,0,0,0,0, + 14791,57083,65535,65535,50744,32,0,0, + 0,32,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,23243,65535,65535,65535,65535,65535, + 54970,8452,0,27501,65503,65535,50744,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,50744,63422,10565, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,12678,50744,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65503,65535,63422, + 33840,4226,0,0,0,0,25356,65535, + 65535,65535,44373,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,4226,12678,25356,38066, + 52825,63390,65503,63390,33840,32,0,0, + 0,10565,54938,65535,65503,16904,0,0, + 2113,19017,35921,38066,29582,10597,32,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,10565,59196,65535,65535,65503,65535,63422, + 16936,0,0,35921,65535,65535,29614,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,48599,65503,8484, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2145,27469, + 54938,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,61277,42260,10597, + 32,0,0,0,0,0,25388,65535, + 65535,65535,27469,0,0,0,0,0, + 0,0,0,32,0,0,0,0, + 0,0,0,0,32,0,32,0, + 2113,6339,14823,27501,42260,19049,32,0, + 0,0,8452,52857,65535,44405,32,8452, + 46486,63422,65535,65535,65535,61277,44405,14823, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 4258,50712,65535,65535,65503,65535,65503,27501, + 0,0,0,44373,65535,63390,12678,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,46486,63422,10565, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2145,19017,44373,57083,63390,65503,65535,65503, + 65535,65503,59164,50744,27501,6371,32,0, + 0,0,0,0,0,0,23243,65535, + 65535,65535,19049,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,8484,57083,63390,14823,52825, + 65535,65535,65535,65535,65535,65535,65503,63422, + 44405,10565,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,32, + 33840,65535,65535,65535,65535,65535,42260,32, + 0,32,32,50744,65503,61309,2145,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,48599,63390,8452, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2145,8452,14823,21162,21162, + 16936,10597,4226,32,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,16936,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,19049,63422,61277,65535, + 65535,65535,65535,63422,61277,63422,65535,65535, + 65535,61277,33840,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,19049, + 63390,65535,65535,65535,65535,52825,4258,0, + 0,32,4226,59196,65535,48599,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,52857,63390,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,0,32,0,0,0,0, + 0,0,0,0,0,0,32,0, + 0,0,0,0,0,0,14823,65535, + 65535,65503,14791,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,32,32,50744,65535,65535, + 65535,65535,57083,19049,4226,8452,23275,50744, + 65535,65535,65503,52857,16936,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10565,59164, + 65535,65535,65535,65535,59196,10597,0,0, + 0,0,8452,65503,65535,35953,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2145,57051,63422,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,12678,65535, + 65535,65503,12678,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,33840,65535,65535, + 65535,57083,10597,0,0,32,0,2113, + 25356,59196,65535,65535,63390,33840,4226,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,0,0,0,2113,48599,65535, + 65535,65535,65535,63390,21130,0,32,0, + 0,0,21130,65535,65535,27501,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,61277,63390,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63390, + 65535,63422,10597,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,19049,65535,65535, + 65503,16936,2145,16936,29614,29614,21162,10565, + 2145,10597,52825,65535,65535,65535,54938,14823, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,33840,65503,65535, + 65535,65535,65535,33808,2113,0,0,0, + 0,0,31695,65503,65535,23243,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,10565,63422,61309,6339, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8452,61309, + 65535,63390,10565,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,14823,63422,65535, + 48631,32,29614,59164,65535,65535,65535,65503, + 52857,33840,23243,61309,65535,63422,65535,63390, + 31695,2145,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,0,19049,65503,65535,65535, + 65535,65503,46486,2113,0,0,0,0, + 0,0,42292,65503,63422,16904,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,16936,65535,61309,6339, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6371,61277, + 65535,61309,8484,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,61309,65535, + 33808,0,0,6371,31695,54938,65535,65535, + 65535,65535,65503,63422,65535,65535,65535,65535, + 65535,48599,8484,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6371,59164,65535,65535,65535, + 65535,57083,6371,0,0,0,0,0, + 0,2145,48631,65535,63390,12678,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,25356,63422,61309,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6339,59196, + 65535,61277,8452,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,4226,59196,65535, + 21130,0,32,32,0,2113,21130,44405, + 61309,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,59164,21162,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,2113,44373,65535,65535,65535,65535, + 61277,16936,0,0,0,0,0,0, + 0,2113,54970,65535,63390,6339,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,33840,65535,61277,6339, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,6339,61277, + 65535,61309,8452,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,59164,65503, + 10565,0,0,0,0,0,0,32, + 8484,31727,57051,65535,65535,65535,65535,65535, + 65535,65535,65535,65503,33808,2113,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,27469,63390,65503,65535,65535,65535, + 31695,0,0,0,0,0,0,0, + 0,4258,59196,65535,61277,2145,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,40179,65535,61277,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6371,61309, + 65535,61277,6371,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,59164,61309, + 6339,0,0,0,0,0,0,32, + 4258,4226,8484,42292,65535,65535,65535,65535, + 65535,65535,65535,65535,65503,42260,2145,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,10597,61277,65535,65535,65535,65503,46486, + 2113,0,0,0,0,0,0,0, + 0,8452,63390,65535,52857,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,46518,65535,61277,4226, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63390, + 65535,59196,6339,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,59164,57051, + 2145,0,32,0,32,0,32,4258, + 50744,61309,61309,63422,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,40147,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2113,46486,65535,65535,65535,65535,59164,8484, + 0,0,32,0,0,0,0,0, + 0,23243,65503,65535,48599,2113,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,50744,65535,61309,6339, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10565,63422, + 65535,59164,4258,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,59164,52857, + 32,0,0,0,0,0,0,0, + 12678,52825,63422,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,63390,23243, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 23275,65535,65535,65535,65535,65503,21162,0, + 0,0,0,0,0,0,0,2113, + 12678,54970,65535,65535,42260,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,52857,65535,61277,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,12678,63422, + 65535,59164,4258,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4258,59196,48599, + 0,0,0,0,0,0,0,0, + 0,2113,16904,29582,44373,54970,63390,65535, + 65535,65535,65535,65535,65535,65535,65535,57083, + 2113,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4258, + 57083,65535,65535,65535,65535,44373,32,0, + 0,0,0,0,0,0,0,31727, + 65503,65535,65535,65535,33840,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,52857,65535,61277,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,12710,65503, + 65535,57083,4226,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,6339,61277,46486, + 0,0,0,0,0,0,0,0, + 0,0,32,0,0,32,10565,31727, + 54938,65503,65503,65535,65535,65535,65535,65503, + 21162,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,29582, + 65535,65535,65535,65535,63390,12678,0,0, + 0,0,0,0,0,0,0,12678, + 63422,65535,65535,65535,25388,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,52857,65535,61277,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16904,65535, + 65535,57051,2145,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8452,61309,44405, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,0, + 2145,25388,57083,65535,65535,65535,65535,65535, + 38066,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,2145,54970, + 65535,65535,65535,65535,42292,0,32,0, + 0,0,0,0,0,0,0,32, + 50712,65535,65535,65503,19049,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,52857,65535,61277,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19017,65535, + 65535,54970,2113,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,63390,44405, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,6371,48599,65503,65535,65535,65535, + 50744,32,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19017,65535, + 65535,65535,65535,63422,14791,32,0,0, + 0,0,0,0,2113,19049,10565,2113, + 31695,65535,65535,65535,12678,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,52857,65535,61277,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,23243,65535, + 65535,54970,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,12678,63390,50712, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,48599,65535,65535,65535, + 57083,2113,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,32,42292,65535, + 65535,65535,65535,57051,2145,0,0,0, + 0,0,0,0,4258,59196,63390,54970, + 50744,65535,65535,63390,6371,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,50744,65535,59196,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,25356,65535, + 65535,52857,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,14791,65535,52857, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,8452,54938,65535,65535, + 61277,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,59196,65535, + 65503,65535,65535,44373,32,0,0,0, + 0,0,0,0,32,44405,65535,65535, + 65503,65535,65535,59196,2145,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,48599,65535,59164,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,29582,65535, + 65535,50744,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,19049,65535,54970, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,19049,65503,65535, + 65503,6339,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,12710,63390,65535, + 65535,65535,65535,33840,0,0,0,0, + 0,0,0,0,0,16936,65503,65535, + 65535,65535,65535,59164,2145,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,46518,65535,59164,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,31727,65535, + 65535,50744,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,25356,65503,57051, + 2113,32,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,48599,65535, + 65535,10597,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,19049,65535,65535, + 65535,65535,65535,27469,0,0,0,0, + 0,0,0,0,0,2145,54938,65535, + 65535,65535,65535,54938,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,46486,65535,59164,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,33840,65535, + 65535,48599,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,29614,65535,57051, + 2113,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,23275,65503, + 65503,16904,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,25356,65535,65535, + 65535,65535,65535,23275,0,0,0,0, + 0,0,0,0,0,0,27501,65503, + 65535,65535,65535,48631,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,44373,65535,59164,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,38034,65535, + 65535,46486,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,35921,65535,57051, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8452,63390, + 65535,21162,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,27469,65535,65535, + 65535,65535,65535,25388,0,0,0,0, + 0,0,0,0,0,0,6339,59164, + 65535,65535,65535,42260,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,42260,65535,59164,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,42260,65535, + 65535,44405,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,42260,65535,54970, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2113,54970, + 65535,29582,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,25356,65535,65535, + 65535,65535,65535,31695,0,0,0,0, + 0,0,0,0,0,0,0,44373, + 65535,65535,65535,38034,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,40179,65535,59164,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,44405,65535, + 65535,44373,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,44373,65535,52857, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,44373, + 65535,33840,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,16936,65535,65535, + 65535,65535,65535,44405,32,0,0,0, + 0,0,0,0,0,0,0,21162, + 65503,65535,65535,29582,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38066,65535,57083,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,46518,65535, + 65535,40147,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,50712,65535,50744, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,35953, + 65535,40147,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,10597,63422,65535, + 65535,65535,65535,54938,2145,0,0,32, + 0,0,0,0,0,32,0,8484, + 65535,65535,65503,21162,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38066,65535,57083,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,50712,65535, + 65535,38066,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,52857,65535,46518, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,29614, + 65535,44373,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,4258,57051,65535, + 65535,65535,65535,65503,10565,0,32,0, + 0,0,0,0,0,0,0,4258, + 61277,65535,63422,16904,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38066,65535,57083,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,54938,65535, + 65535,35953,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,57083,65535,40179, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,27469, + 65535,50712,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,33840,65535, + 65535,65535,65535,65535,33808,0,0,0, + 0,0,0,0,0,0,0,2145, + 59164,65535,63390,10597,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38066,65535,57083,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,57083,65535, + 65535,35921,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,61277,65535,33840, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,27501, + 65535,54938,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10597,61309, + 65535,65535,65535,65535,57083,2145,0,0, + 0,0,0,0,0,0,0,32, + 57051,65535,63390,4226,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38034,65535,57051,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4258,61309,65535, + 65503,33808,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,6339,61309,65535,27501, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,29582, + 65535,57083,2145,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,42260, + 65535,65535,65535,65503,65535,25356,0,0, + 0,0,0,0,0,0,0,2145, + 59164,65535,59164,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38034,65535,57051,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,6339,61309,65535, + 65535,31695,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,10565,65503,65535,19049, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,35953, + 65535,61277,4226,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 61277,65535,65535,65535,65535,54938,2145,0, + 0,0,0,0,0,0,0,4226, + 61309,65535,48599,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38034,65535,57051,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8452,63390,65535, + 65535,29614,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,14791,65503,65503,12678, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,46518, + 65535,63390,6339,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,32, + 27501,65535,65535,65535,65535,65535,27501,0, + 0,0,0,0,0,0,0,6339, + 65503,65535,40147,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38034,65535,57051,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,63390,65535, + 65535,29582,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,21162,65535,63390,6339, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,54938, + 65535,63390,8484,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2113,46486,65535,65535,65535,65535,59196,8484, + 0,0,0,0,0,0,0,14823, + 65535,65535,29582,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38034,65535,54970,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,63422,65535, + 65535,27469,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,25388,65535,57083,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,61309, + 65535,63422,10597,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,6371,57083,65535,65535,65535,65535,46486, + 2145,0,0,0,0,0,32,29582, + 65535,65535,21130,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38034,65535,54970,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,63422,65535, + 65535,25388,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,29614,65535,54938,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6371,63390, + 65535,65503,14791,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,19049,63422,65535,65535,65535,65535, + 27501,0,0,0,32,0,0,40179, + 65535,63390,10597,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35953,65535,54970,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,63422,65535, + 65535,23275,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35921,65535,48631,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,12678,65503, + 65535,65503,19049,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,35953,63422,65535,65535,65535, + 61309,12678,0,0,0,0,32,50744, + 65535,61277,6339,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35953,65535,54970,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,63422,65535, + 65535,23243,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,40147,65535,46486,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16904,65535, + 65535,65503,23243,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,4226,52825,65535,65535,65535, + 65535,50712,4258,0,0,0,4258,59164, + 65535,52857,32,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35921,65535,54970,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,6339,63390,65535, + 65535,21162,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,44405,65535,42292,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,29582,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,12678,61277,65535,65535, + 65535,65535,40147,2113,32,0,21130,65535, + 65535,44405,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35953,65535,54938,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4258,61309,65535, + 65535,19049,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,48599,65535,42292,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,31727,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,31695,63422,65535, + 65535,65535,65503,42292,16936,16904,54970,65535, + 65535,33840,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38066,65535,52857,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4226,61277,65535, + 65535,16936,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,50744,65535,42292,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,35953,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4226,52825,65535, + 65535,65535,65535,65535,65535,65503,65535,65535, + 65535,27469,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38066,65535,52857,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,59196,65535, + 65503,16904,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,54938,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,40179,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16936,63422, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,16936,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,38066,65535,52857,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,57083,65535, + 65503,14791,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,57083,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16904,65535, + 65535,65535,42260,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,32,44405, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65503,10565,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,42260,65535,52825,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,54938,65535, + 65503,14791,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2145,59164,65535,44405,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,14791,65535, + 65535,65535,44373,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10597, + 63390,65535,65503,65535,65535,65535,65535,65535, + 63390,6339,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,44405,65535,52825,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,50712,65535, + 65503,14791,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,4258,59164,65535,46486,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,12678,65535, + 65535,65535,46486,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,0, + 40179,65535,65535,65535,65535,65535,65535,65535, + 61277,6371,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,48599,65503,52825,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,46518,65535, + 63422,12710,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6339,59196,65535,48599,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10597,65503, + 65535,65535,48599,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 10597,63390,65535,65535,65535,65535,65535,65535, + 61277,6339,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,4226,54938,65535,52825,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,44373,65535, + 63422,12678,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,0,6339,59196,65535,50712,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,44373,65535,65535,65535,65535,65535,65535, + 61309,4258,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6339,61277,65535,50712,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,40179,65535, + 63422,12678,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6371,61277,65535,50744,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,12678,63422,65535,65535,65535,65535,65535, + 63422,8452,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,12710,65535,65535,50712,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,35953,65535, + 63422,12678,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,8452,61309,65535,52857,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,48599,65503,65535,65535,65535,65535, + 65503,12710,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,21162,65535,65535,50744,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,33840,65535, + 65503,12710,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,8452,63390,65535,54938,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,19017,63422,65535,65535,65535,65503, + 65503,19049,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,38034,65535,65535,48631,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,31695,65535, + 63422,16936,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,8484,63390,65535,57083,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10565,65535, + 65535,65535,44405,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,2145,52857,65535,65535,65535,65535, + 65535,23243,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,32,52825,65535,65535,48631,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,29582,65535, + 65535,19049,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,10565,63422,65535,59196,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10597,65503, + 65535,65535,40179,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,27469,65503,65535,65535,65535, + 65535,27469,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,57051,65535,65535,48631,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,27469,65535, + 65535,25356,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,12678,65503,65535,63390,8484, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19017,65503, + 65535,65535,31695,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6339,59164,65535,65535,65535, + 65535,27501,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,10565,61309,65535,65535,48631,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,25388,65535, + 65535,31695,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,12710,65535,65535,63422,10597, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,27469,65535, + 65535,65535,21162,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35953,65535,65535,65535, + 65535,29582,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,12678,65503,65535,65535,57051,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,23243,65535, + 65535,42260,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,14823,65503,65535,65535,16904, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,32,48599,65535, + 65535,63422,14791,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,8484,61309,65535,65535, + 65535,27501,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,16904,65535,65535,65535,61277,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,23243,65535, + 65535,50744,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,16936,65503,65535,63422,21130, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,16904,65503,65535, + 65535,61309,4226,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,52857,65535,65535, + 65535,25388,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,16904,65535,65535,65535,65503,12678, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,23243,65535, + 65535,59196,4258,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,25356,65535,65535,65535,27469, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,50744,65535,65535, + 65535,63390,6339,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,48599,65535,65535, + 65535,25356,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,12678,65503,65535,65535,65535,23243, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,23243,65535, + 65535,63390,10597,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,35921,65535,65535,65535,33840, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,25356,65503,65503,65535, + 65535,65503,19017,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,48599,65535,65535, + 65535,19049,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,6339,59196,65535,65535,65535,35953, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,23243,65535, + 65535,65535,19049,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,52825,65535,65535,65535,44373, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,42292,65535,65535,65535, + 65535,65535,38066,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,50744,65535,65535, + 65503,16936,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,42260,65535,65535,65535,38066, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,25388,65535, + 65535,65535,31727,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,4258,59196,65535,65535,65535,54970, + 2113,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35953,65535,65535,65535, + 65535,65535,54970,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2145,54970,65535,65535, + 65503,14791,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,12678,63390,65535,63390,19049, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,29614,65535, + 65535,65535,44373,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,10597,63422,65535,65535,65535,65503, + 12678,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,12678,63390,65535,65535, + 65535,65535,63422,8484,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4226,59164,65535,65535, + 63422,12678,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,33808,63422,29614,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,33808,65535, + 65535,65503,48631,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,14823,63390,65535,65535,65535,65535, + 25388,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,42260,65535,65535, + 65535,65535,65535,25356,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,61309,65535,65535, + 63422,10597,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2145,21162,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,23243,63390, + 65535,65535,42292,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,14791,63422,65535,65535,65535,65535, + 46518,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,10565,63390,65535, + 65535,65535,65535,40179,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,61309,65535,65535, + 63422,10597,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,21162, + 59164,63390,19017,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,2145,52825,65535,65535,65535,65535, + 57083,4258,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,31695,65503, + 65535,65535,65535,54938,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,6371,63390,65535,65535, + 65503,12678,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 6339,12710,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,8452,40147,57083,61309,59164, + 31727,2145,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,50744, + 65503,65535,65535,59196,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,8452,63422,65535,65535, + 65503,12678,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,6371,4226, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 61277,65535,65535,63422,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,6339,63422,65535,65535, + 65503,16936,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 38034,65535,65535,57083,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,61309,65535,65535, + 65535,19049,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 19017,65535,65535,48631,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,59196,65535,65535, + 65535,21162,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 21130,65535,65535,42260,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,59164,65535,65535, + 65535,23243,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 33808,65535,65535,31695,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,52857,65535,65535, + 65535,21162,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,32, + 48631,65535,65503,19017,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,48631,65535,65535, + 65535,21162,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,4258, + 61309,65535,63422,8484,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,40147,65535,65535, + 65535,23275,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,21162, + 65503,65535,57083,2145,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,29582,65503,65535, + 65535,25388,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,12710, + 2145,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,14823,25356,6371,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,40179, + 65535,65535,44373,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,16936,65503,65535, + 65503,27501,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,0,0,10597,54938, + 6339,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,12710,63390,59164,19049,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,59196, + 65535,65503,21130,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,8484,65503,65535, + 65535,31727,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,0,10565,54938,63390, + 8452,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,50744,65535,61309,14791,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,21130,65535, + 65535,59196,4258,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,52857,65503, + 65535,40179,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,32,0,0,12710,57051,65535,61277, + 6339,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,27469,65535,65535,50744,2113,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,46486,65535, + 65535,42260,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,38034,65535, + 65535,48631,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,14791,59164,65535,65535,52857, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,52825,65503,65503,19017,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,14823,65503,65535, + 63390,10565,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19017,65535, + 65535,59164,4258,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,14823,59196,65535,65535,65535,35921, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,14823,65503,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2145,50744,65535,65535, + 35921,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,57083, + 65535,65503,16904,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2113,8452,59164,65503,65535,65535,61277,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,48599,65535,54970,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,33840,65535,65535,52857, + 4226,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,40147, + 65535,65535,35921,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,40179,65535,65535,65535,65535,31695,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,31727,65535,59196,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,25388,65503,65535,57051,8484, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,12710, + 65503,65535,59164,6339,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 8452,63390,65535,65535,65535,54938,6339,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,27469,65535,59164,4226, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2145,27469,63390,65503,57051,14791,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 40147,65535,65503,31727,32,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 19049,65503,65535,65535,65503,25388,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,40147,65535,52825,32, + 0,0,0,0,0,32,32,0, + 0,0,0,0,0,32,0,32, + 8452,44373,65535,65535,52857,8484,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 6339,57083,65503,59164,6371,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 35921,65535,65535,65535,59164,4226,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,10597,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,4258,57083,65535,33808,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,29614, + 59164,65535,65535,52825,6371,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,16936,63390,65535,44373,2113,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 50744,65535,65535,65535,40147,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,42260,19017,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,23275,65535,61277,8452,0, + 0,0,0,0,0,0,0,32, + 0,0,32,4226,14823,35953,61277,65535, + 65535,65535,48599,6339,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,35921,65535,65503,23275,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2145, + 59164,65535,65535,65535,25356,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,19049,61277,21162, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,44405,65503,29614,32,0, + 0,0,0,0,0,0,32,4226, + 14823,27469,46486,61277,65503,65535,65535,65535, + 65503,38066,2145,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,2113,48631,65535,59196,12710,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,6339, + 61277,65535,65535,63422,19017,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,52857,63390, + 23275,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,21162,33808,2113,0,32, + 0,0,0,0,2145,27469,50744,61277, + 65535,65535,65535,65535,65535,65535,65535,59164, + 25388,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,12678,61309,65535,54938,8452, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,6371, + 61277,65535,65535,65535,19049,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,25388,65503, + 61309,25356,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,2113,32,0,0,0, + 0,32,0,19017,54938,65503,65535,65535, + 65535,65535,65503,65535,65535,61309,42292,8484, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,27469,65503,65535,48631, + 4258,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4258, + 59196,65535,65535,65535,23275,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,59196, + 65535,63390,14823,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,32,0, + 0,32,29614,63422,65535,65503,65535,65535, + 65503,65503,61277,54938,35953,10565,0,0, + 0,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,0,0,2113,46486,65535,65535, + 48631,6339,32,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,4226, + 57083,65535,65535,65535,31695,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,38066, + 65535,65535,52857,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2145,40147,65535,65535,61277,42292,23275,14791, + 12710,10597,6339,32,0,0,0,0, + 0,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,6371,57051,65503, + 65535,52857,8484,32,0,0,32,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 46486,65535,65535,65535,42292,0,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,14791, + 65503,65535,65503,14823,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,4226, + 44373,65503,65503,42260,6371,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,14823,63390, + 65535,65535,59196,19017,0,0,32,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 23243,65535,65535,65535,48631,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4226, + 59196,65535,65535,25356,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,46486, + 65535,63422,33840,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,27469, + 63422,65535,65535,63390,35921,6371,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2145,54938,65535,65535,57051,2113,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2145, + 57051,65535,65535,25388,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,42292,65535, + 65535,44373,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,0, + 31727,65503,65535,65535,65535,57051,21162,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,23243,63422,65535,61277,4258,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2145, + 57083,65535,63422,16904,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,40179,65535,65535, + 57083,8452,0,32,0,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,29614,65503,65535,65535,65535,65535,50744, + 21162,2113,0,0,0,32,0,0, + 0,32,0,32,0,0,0,0, + 0,32,40147,65535,63422,6371,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4226, + 59164,65503,57051,2145,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,31695,65535,65535,65535, + 25356,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,19049,59164,65535,65535,65535,65535, + 65503,52825,27469,8452,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,48631,63390,6371,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,6339, + 61309,65503,25356,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,23275,63422,65535,65535,46486, + 2113,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6339,38066,61277,65535,65535, + 65535,65535,65535,61277,46486,21130,4258,32, + 32,0,0,0,0,0,0,0, + 0,0,0,10597,52825,4226,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4226, + 54938,33840,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,16936,61309,65535,65535,61277,8484, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,10597,38034,59196, + 65535,65535,65535,65535,65535,65535,61277,46518, + 27469,12710,2145,32,0,0,0,0, + 0,0,0,0,10565,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 4226,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,10565,57051,65535,65535,65503,23243,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,8484, + 31727,57083,65503,65535,65535,65535,65535,65535, + 65503,65503,57051,44373,19049,4258,0,0, + 32,32,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 6339,50744,65535,65535,65535,31727,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,4226,23243,50712,63390,65535,65535,65535, + 65535,65535,65535,65535,63422,52857,19049,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4258, + 48599,65535,65535,65503,40147,2113,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,0, + 0,0,0,2113,14791,40179,61277,65535, + 65535,65535,65535,65535,65535,65503,65503,38034, + 4226,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2145,42260, + 65503,65535,65503,46518,2145,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,6371,29614, + 52857,65503,65535,65535,65535,65535,65535,65503, + 48631,8452,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,4258,46486,65503, + 65535,65535,50744,6371,0,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 4226,19049,46486,63390,65535,65535,65535,65535, + 65535,52857,10565,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,8452,46518,65535,65535, + 65535,54970,8484,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,10565,33840,52857,65535,65535, + 65503,65535,54970,10597,0,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,14823,54938,65535,65535,65535, + 57083,10597,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,32,0,32,0,4226,21130,50712, + 65535,65535,65535,57051,12710,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,4258,35921,63390,65535,65535,65503,59164, + 14823,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,2113, + 29614,63422,65535,65535,54970,10597,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,4226, + 27469,54970,65535,65535,65535,65535,61309,19017, + 0,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,27469,65535,65535,65535,54970,12678,0, + 0,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,10565,31695,59164, + 65535,65535,65535,65535,65535,61309,23275,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,38034,65535,65535,65535,57083,12678, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2113,4226, + 4226,4258,8452,10597,12678,14791,12678,6371, + 6339,4258,2145,2113,2113,0,0,2113, + 2145,4258,16936,33808,52825,63390,65535,65535, + 65535,65535,65535,65535,63422,27469,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,8452,59164,65535,65535,65535,54938, + 8484,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2145,10597,29582,40147,44405,52825,57083, + 61309,63422,65535,65535,65535,65535,65503,63422, + 63422,61277,54970,50744,48631,46518,46518,48631, + 57083,63390,65535,65535,65535,65503,65535,65535, + 65535,65535,65535,65503,29614,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,31727,65503,65535,65535,65535, + 54970,10565,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,6339, + 31695,54938,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,63422,33840,32,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6371,57083,65503,65535,65503, + 65503,54970,10565,0,0,32,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,8452,35953,59196, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,63390,25388,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,27469,65535,65535,65535, + 65535,65535,57051,10597,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6339,31727,59196,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 63390,59196,54970,52825,50744,48631,48599,48599, + 50712,54970,61309,65503,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 48631,12710,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2145,48631,65535,65503, + 65535,65535,65535,57083,14791,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,21162,54970,65503,65535,65535,65535, + 65535,65535,65535,65535,65503,54970,40179,21162, + 6371,2113,0,32,0,0,0,32, + 0,0,6371,19017,38034,50712,61309,65503, + 65535,65535,65535,65535,65503,61277,48599,19049, + 32,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,10565,59164,65535, + 65535,65503,65535,65535,61277,21130,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 16904,48599,65503,65535,65535,65535,65535,65535, + 65535,65535,63422,46486,16936,2145,0,32, + 0,32,0,0,32,0,0,0, + 0,0,0,0,0,32,6371,14823, + 25388,31695,31695,25388,16904,6371,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16936,61309, + 65535,65535,65503,65535,65535,63390,35953,6371, + 0,0,32,0,32,0,0,0, + 0,0,0,0,2113,6371,25356,46518, + 65503,65535,65535,65535,65535,65535,65535,61309, + 50712,33808,10597,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,21162, + 61309,65535,65535,65535,65535,65535,65535,61277, + 35921,14823,4258,32,32,0,0,32, + 2145,4258,10597,27469,46486,61309,65503,65503, + 65535,65535,65503,65535,65535,54970,25356,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,0, + 21162,63390,65535,65535,65535,65535,63422,65535, + 65535,63390,59196,52857,48599,44405,44405,48599, + 52857,61277,63390,65503,65535,65535,65535,65535, + 65535,65535,65535,65503,50744,8452,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,21130,59164,65503,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,63422,61277,50744,10597,0,32,0, + 0,32,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,10597,50744,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,61277,46486, + 31695,19017,4258,2113,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,2145,29582,57083,63422,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,52857,25356,4258,2113, + 0,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4258,27501,48631, + 61309,65503,65503,65535,65535,65535,65503,63422, + 63390,59196,44405,23243,2113,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 4258,16904,25388,33840,35953,35921,29614,23243, + 10597,4226,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0 +}; + +const basic_image<unsigned short> respect(height,width,pixelData); \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/res/respect.h b/src/tests/drivers/xbee/gui/res/respect.h new file mode 100644 index 0000000000000000000000000000000000000000..543531b60ad8c0c932e4756a1a0e0fa897e355ad --- /dev/null +++ b/src/tests/drivers/xbee/gui/res/respect.h @@ -0,0 +1,11 @@ + +//This file has been automatcally generated by pngconverter utility +//Please do not edit +#ifndef RESPECT_H +#define RESPECT_H + +#include "mxgui/image.h" + +extern const mxgui::Image respect; + +#endif //RESPECT_H diff --git a/src/tests/drivers/xbee/logdecoder/.gitignore b/src/tests/drivers/xbee/logdecoder/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8b193293c00f3d4483c23b0d5c2e705d0617e209 --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/.gitignore @@ -0,0 +1 @@ +logdecoder \ No newline at end of file diff --git a/src/tests/drivers/xbee/logdecoder/LogTypes.h b/src/tests/drivers/xbee/logdecoder/LogTypes.h new file mode 100644 index 0000000000000000000000000000000000000000..2b7b17baec1dfa8d29e47f5eaf3fe5fa23279f1b --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/LogTypes.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2015-2018 Skyward Experimental Rocketry + * Authors: Luca Erbetta + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <fstream> +#include <iostream> + +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/XbeeStatus.h" +#include "drivers/xbee/Mark.h" +#include "drivers/xbee/XbeeTestData.h" +#include "logger/Deserializer.h" +#include "logger/LogStats.h" + +// Serialized classes +using std::ofstream; + +template <typename T> +void print(T& t, ostream& os) +{ + t.print(os); +} + +template <typename T> +void registerType(Deserializer& ds) +{ + ds.registerType<T>(print<T>, T::header()); +} + +void registerTypes(Deserializer& ds) +{ + registerType<LogStats>(ds); + + registerType<Xbee::APIFrameLog>(ds); + registerType<Xbee::ATCommandFrameLog>(ds); + registerType<Xbee::ATCommandResponseFrameLog>(ds); + registerType<Xbee::ModemStatusFrameLog>(ds); + registerType<Xbee::TXRequestFrameLog>(ds); + registerType<Xbee::TXStatusFrameLog>(ds); + registerType<Xbee::RXPacketFrameLog>(ds); + registerType<Xbee::XbeeStatus>(ds); + + registerType<TxData>(ds); + registerType<RxData>(ds); + registerType<Mark>(ds); + registerType<XbeeConfig>(ds); + registerType<EnergyScanData>(ds); +} diff --git a/src/tests/drivers/xbee/logdecoder/Makefile b/src/tests/drivers/xbee/logdecoder/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..47258b629723d159885432ac1e485023f5d40002 --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/Makefile @@ -0,0 +1,13 @@ +BASE := ../../../../../ + +all: + g++ -std=c++17 -O2 -o logdecoder logdecoder.cpp \ + -DCOMPILE_FOR_X86 \ + $(BASE)libs/tscpp/stream.cpp \ + -I$(BASE)src/shared \ + -I$(BASE)src/tests \ + -I$(BASE)libs \ + -I$(BASE)libs/miosix-kernel/miosix + +clean: + rm logdecoder diff --git a/src/tests/drivers/xbee/logdecoder/logdecoder.cpp b/src/tests/drivers/xbee/logdecoder/logdecoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f1f725ddf79da1d1610633fce3594fa66824360 --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/logdecoder.cpp @@ -0,0 +1,168 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +/* + * This is a stub program for the program that will decode the logged data. + * Fill in the TODO to make it work. + */ + +#include <sys/stat.h> +#include <sys/types.h> +#include <tscpp/stream.h> + +#include <filesystem> +#include <fstream> +#include <iostream> +#include <stdexcept> +#include <string> +#include <vector> + +#include "LogTypes.h" + +using namespace std; +using namespace tscpp; + +namespace fs = filesystem; + +void showHelp(string cmdName) +{ + std::cerr << "Usage: " << cmdName + << " {-a [logs_diretory] | <log_file_path> | -h}" + << "Options:\n" + << "\t-h,--help\t\tShow help message\n" + << "\t-a,--all [dir=\".\"] Deserialize all logs in the provided directory\n" + << std::endl; +} + +vector<fs::path> listLogFiles(fs::path dir) +{ + vector<fs::path> out; + for (const auto& entry : fs::directory_iterator(dir)) + { + if (entry.exists() && entry.is_regular_file()) + { + if (entry.path().extension() == ".dat") + { + out.push_back(entry.path()); + } + } + } + return out; +} + +bool deserialize(fs::path log_path) +{ + cout << "Deserializing " << log_path << ".dat...\n"; + + // remove extension + log_path.replace_extension(""); + Deserializer d(log_path); + registerTypes(d); + + return d.deserialize(); +} + +bool deserializeAll(fs::path dir = ".") +{ + vector<fs::path> logs = listLogFiles(dir); + for (auto log : logs) + { + if (!deserialize(log.string())) + { + return false; + } + } + + return true; +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + showHelp(string(argv[0])); + return 1; + } + + bool success = false; + string arg1 = string(argv[1]); + if (arg1 == "-h" || arg1 == "--help") + { + showHelp(string(argv[0])); + return 0; + } + + if (arg1 == "-a" || arg1 == "--all") + { + fs::path dir = "."; + if (argc == 3) + { + string arg2 = string(argv[2]); + fs::directory_entry entry(arg2); + if (entry.exists() && entry.is_directory()) + { + dir = arg2; + } + else + { + cout << "Second argument after '-a' or '--all' must be a " + "directory\n"; + showHelp(string(argv[0])); + return 1; + } + } + cout << "Deserializing all logs...\n"; + success = deserializeAll(dir); + } + else if (arg1[0] == '-') + { + cerr << "Unknown option\n"; + return 1; + } + else + { + fs::directory_entry entry(arg1); + if (entry.exists() && entry.is_regular_file()) + { + success = deserialize(arg1); + } + else + { + showHelp(string(argv[0])); + return 1; + } + } + + if (success) + { + cout << "Deserialization completed successfully.\n"; + } + else + { + cout << "Deserialization ended with errors.\n"; + } +} diff --git a/src/tests/drivers/xbee/test-xbee-bidir.cpp b/src/tests/drivers/xbee/test-xbee-bidir.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c8abcb445427b4e1e8813d488f12eb5cce0bbd89 --- /dev/null +++ b/src/tests/drivers/xbee/test-xbee-bidir.cpp @@ -0,0 +1,267 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef RUN_SENDER +#define RUN_SENDER true +#endif +#ifndef RUN_RECEIVER +#define RUN_RECEIVER true +#endif + +#include <miosix.h> + +#include <cstdio> +#include <stdexcept> + +#include "ActiveObject.h" +#include "XbeeTransceiver.h" +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/ATCommands.h" +#include "drivers/Xbee/Xbee.h" +#include "drivers/interrupt/external_interrupts.h" +#include "logger/Logger.h" + +using namespace miosix; + +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X +#include "interfaces-impl/hwmapping.h" +using GpioMiso = miosix::interfaces::spi2::miso; +using GpioMosi = miosix::interfaces::spi2::mosi; +using GpioSck = miosix::interfaces::spi2::sck; + +using GpioCS = xbee::cs; +using GpioATTN = xbee::attn; +using GpioRST = xbee::reset; + +using GpioLedLog = Gpio<GPIOG_BASE, 2>; + +#define XBEE_SPI SPI2 +#else +using GpioMiso = Gpio<GPIOB_BASE, 4>; +using GpioMosi = Gpio<GPIOA_BASE, 7>; +using GpioSck = Gpio<GPIOA_BASE, 5>; + +using GpioCS = Gpio<GPIOC_BASE, 1>; +using GpioATTN = Gpio<GPIOE_BASE, 5>; +using GpioRST = Gpio<GPIOE_BASE, 6>; + +using GpioLedLog = Gpio<GPIOC_BASE, 13>; +#define XBEE_SPI SPI1 +#endif + +using GpioUserBtn = Gpio<GPIOA_BASE, 0>; + +Xbee::Xbee* xbee_driver = nullptr; +Logger& logger = Logger::instance(); + +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X +void __attribute__((used)) EXTI10_IRQHandlerImpl() +#else +void __attribute__((used)) EXTI5_IRQHandlerImpl() +#endif +{ + if (xbee_driver != nullptr) + { + xbee_driver->handleATTNInterrupt(); + } +} + +int getUserBtnValue() +{ +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X + return 0; +#else + return GpioUserBtn::value(); +#endif +} + +void configure() +{ +#ifndef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X + { + FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + + // Set SPI pins to correct alternate mode + GpioSck::mode(Mode::ALTERNATE); + GpioMiso::mode(Mode::ALTERNATE); + GpioMosi::mode(Mode::ALTERNATE); + + GpioSck::alternateFunction(5); + GpioMiso::alternateFunction(5); + GpioMosi::alternateFunction(5); + + GpioATTN::mode(Mode::INPUT); + + GpioLedLog::mode(Mode::OUTPUT); + + GpioUserBtn::mode(Mode::INPUT_PULL_DOWN); + + GpioCS::mode(Mode::OUTPUT); + } + + GpioCS::high(); + GpioLedLog::low(); +#endif + +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X + enableExternalInterrupt(GPIOF_BASE, 10, InterruptTrigger::FALLING_EDGE); +#else + enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE); +#endif +} + +void setupXbee(XbeeConfig config) +{ + if (xbee_driver) + { + if (config.data_rate_80k) + { + if (!Xbee::setDataRate(*xbee_driver, true)) + { + TRACE("[main] Error setting xbee_driver data rate!\n"); + } + } + + if (!config.freq_hop) + { + if (!Xbee::disableFrequencyHopping(*xbee_driver)) + { + TRACE("[main] Error disabling frequency hop!\n"); + } + } + } +} + +int main() +{ + XbeeConfig config; + config.data_rate_80k = true; + config.freq_hop = true; + + config.packet_size = 256; + config.send_interval = 333; + config.tx_enabled = RUN_SENDER; + config.timestamp = getTick(); + + configure(); + + int filenum; + try + { + filenum = logger.start(); + + printf("\nLog file opened! (%s)\n\n", + logger.getFileName(filenum).c_str()); + } + catch (const std::runtime_error& err) + { + GpioLedLog::high(); + printf("\n!!!!!!Error opening log file!!!!!!!\n\n"); + } + + logger.log(config); + + SPIBus spi_bus(XBEE_SPI); + SPIBusConfig cfg{}; + cfg.clock_div = SPIClockDivider::DIV16; + + GpioPin cs = GpioCS::getPin(); + GpioPin attn = GpioATTN::getPin(); + GpioPin rst = GpioRST::getPin(); + + xbee_driver = new Xbee::Xbee(spi_bus, cfg, cs, attn, rst); + + setupXbee(config); + + // RandSendInterval intv(333, 1500); + ConstSendInterval intv(config.send_interval); + XbeeTransceiver* trans = + new XbeeTransceiver(*xbee_driver, logger, intv, 256, 333); + if (!config.tx_enabled) + { + trans->disableSender(); + } + if (!RUN_RECEIVER) + { + trans->disableReceiver(); + } + trans->start(); + + while (getUserBtnValue() == 0) + { + long long loop_start = getTick(); + + DataRateResult res_rcv = trans->getReceiver().getDataRate(); + DataRateResult res_snd = trans->getSender().getDataRate(); + + TxData txd = trans->getSender().getTxData(); + RxData rxd = trans->getReceiver().getRxData(); + + logger.log(xbee_driver->getStatus()); + logger.log(logger.getLogStats()); + + long long tick = getTick(); + unsigned int h = tick / (1000 * 3600); + unsigned int m = (tick - h * 1000 * 3600) / (1000 * 60); + float s = (tick - h * 1000 * 3600 - m * 1000 * 60) / 1000.0f; + + printf("%02u:%02u:%06.3f\n", h, m, s); + if (RUN_SENDER) + { + printf("SND: int: %d, cnt: %d, tts: %u ms, pps: %.1f, fail: % d\n ", + txd.time_since_last_send, + txd.tx_success_counter + txd.tx_fail_counter, + txd.time_to_send, res_snd.packets_per_second, + txd.tx_fail_counter); + } + if (RUN_RECEIVER) + { + printf( + "RCV: cnt: %d, last_rx: %lld ms, RSSI: %d, dr: %.0f, pps: " + "%.1f," + " pl: %.0f%%, lcnt: %u, fail: %u\n", + rxd.rcv_count, rxd.last_packet_timestamp, rxd.RSSI, + res_rcv.data_rate, res_rcv.packets_per_second, + res_rcv.packet_loss * 100, rxd.packets_lost, rxd.rcv_errors); + } + printf("\n"); + + Thread::sleepUntil(loop_start + 1000); + } + + trans->stop(); + delete trans; + delete xbee_driver; + logger.stop(); + printf("Log closed.\n"); + + for (;;) + { + GpioLedLog::high(); + Thread::sleep(50); + GpioLedLog::low(); + Thread::sleep(5000); + } +} \ No newline at end of file diff --git a/src/tests/drivers/xbee/test-xbee-gui.cpp b/src/tests/drivers/xbee/test-xbee-gui.cpp new file mode 100644 index 0000000000000000000000000000000000000000..60019fa0c190d5c809b6719e0f09f0d45e3e8070 --- /dev/null +++ b/src/tests/drivers/xbee/test-xbee-gui.cpp @@ -0,0 +1,368 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <miosix.h> +#include <mxgui/display.h> + +#include <array> +#include <cstdio> +#include <functional> +#include <stdexcept> + +#include "ActiveObject.h" +#include "Mark.h" +#include "XbeeTransceiver.h" +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/ATCommands.h" +#include "drivers/Xbee/Xbee.h" +#include "drivers/interrupt/external_interrupts.h" +#include "gui/XbeeGui.h" +#include "logger/Logger.h" +#include "utils/ButtonHandler.h" + +using namespace miosix; +using namespace mxgui; +using namespace std::placeholders; + +using std::array; +using std::bind; +using std::to_string; + +/// Pin definitions +using GpioMiso = Gpio<GPIOB_BASE, 4>; +using GpioMosi = Gpio<GPIOA_BASE, 7>; +using GpioSck = Gpio<GPIOA_BASE, 5>; + +using GpioCS = Gpio<GPIOC_BASE, 1>; +using GpioATTN = Gpio<GPIOE_BASE, 5>; +using GpioRST = Gpio<GPIOE_BASE, 6>; + +using GpioUserBtn = Gpio<GPIOA_BASE, 0>; +using GpioLedLog = Gpio<GPIOC_BASE, 13>; + +// Forward dec +void onStartButtonClick(View* btn, Interaction action); +void onEnergyButtonClick(View* btn, Interaction action); +void onMarkButtonClick(View* btn, Interaction action); +void onStopButtonClick(View* btn, Interaction action); +void startTransceiver(XbeeConfig config); +void setupXbee(XbeeConfig config); +void configure(); + +// Global variables +Logger& logger = Logger::instance(); +Xbee::Xbee* xbee = nullptr; +ConstSendInterval snd_int{0}; +XbeeTransceiver* trans = nullptr; +XbeeGUI* gui; +ButtonHandler<GpioUserBtn>* btn_handler; + +unsigned int mark_counter = 1; + +/** + * @brief Activeobject to perform an energy detect scan as frequently as + * possible + */ +class EnergyScanner : public ActiveObject +{ +protected: + void run() override + { + while (!shouldStop()) + { + Xbee::ATCommandResponseFrame response; + + uint8_t duration = 0xFF; + + if (xbee->sendATCommand("ED", &response, &duration, 1, 1000) && + response.getCommandDataLength() == 30) + { + array<int, 30> scan; + + for (uint16_t i = 0; i < response.getCommandDataLength(); i++) + { + scan[i] = -(int)(*(response.getCommandDataPointer() + i)); + } + + gui->screen_energy.updateScan(scan); + + EnergyScanData data{getTick(), scan}; + logger.log(data); + } + } + } +} energy_scanner; + +int main() +{ + // Hardware + configure(); + + // SD + try + { + logger.start(); + printf("\nLog file opened! (%s)\n\n", + logger.getFileName(logger.getLogNumber()).c_str()); + } + catch (const std::runtime_error& err) + { + GpioLedLog::high(); + printf("\n!!!!!!Error opening log file!!!!!!!\n\n"); + } + + // XBee + SPIBus spi_bus(SPI1); + SPIBusConfig cfg{}; + cfg.clock_div = SPIClockDivider::DIV16; + + GpioPin cs = GpioCS::getPin(); + GpioPin attn = GpioATTN::getPin(); + GpioPin rst = GpioRST::getPin(); + + xbee = new Xbee::Xbee(spi_bus, cfg, cs, attn, rst); + + // GUI + gui = new XbeeGUI(); + + btn_handler = new ButtonHandler<GpioUserBtn>( + 0, bind(&ScreenManager::onButtonPress, &gui->screen_manager, _2)); + + btn_handler->start(); + + gui->screen_config.btn_start.addOnInteractionListener(onStartButtonClick); + gui->screen_config.btn_energy.addOnInteractionListener(onEnergyButtonClick); + + gui->screen_status.btn_stop.addOnInteractionListener(onStopButtonClick); + gui->screen_status.btn_mark.addOnInteractionListener(onMarkButtonClick); + + gui->screen_energy.btn_stop.addOnInteractionListener(onStopButtonClick); + gui->screen_energy.btn_mark.addOnInteractionListener(onMarkButtonClick); + gui->screen_energy.btn_reset.addOnInteractionListener( + [&](View* d, Interaction action) { + UNUSED(d); + if (action == Interaction::CLICK) + gui->screen_energy.resetStats(); + }); + + gui->screen_end.tv_f.addOnInteractionListener( + [&](View* d, Interaction action) { + UNUSED(d); + if (action == Interaction::CLICK) + gui->screen_manager.showScreen(XbeeGUI::SCREEN_RESPECT); + }); + + gui->screen_end.tv_reset.addOnInteractionListener( + [&](View* d, Interaction action) { + UNUSED(d); + if (action == Interaction::CLICK) + miosix::reboot(); + }); + + // Main loop: updates the information in the GUI + for (;;) + { + long long start = getTick(); + // Update display values + switch (gui->screen_manager.getScreen()) + { + case XbeeGUI::SCREEN_CONFIG: + gui->screen_config.updateLogStatus(logger); + break; + case XbeeGUI::SCREEN_STATUS: + if (trans && xbee) + { + gui->screen_status.updateXbeeStatus( + trans->getReceiver().getDataRate(), + trans->getSender().getDataRate(), + trans->getSender().getTxData(), + trans->getReceiver().getRxData(), xbee->getStatus()); + + logger.log(xbee->getStatus()); + } + + gui->screen_status.updateLogStatus(logger); + break; + case XbeeGUI::SCREEN_ENERGYSCAN: + gui->screen_energy.updateLogStatus(logger); + break; + default: + break; + } + + logger.log(logger.getLogStats()); + Thread::sleepUntil(start + 500); + } +} + +void onStartButtonClick(View* btn, Interaction action) +{ + UNUSED(btn); + if (action == Interaction::CLICK) + { + + XbeeConfig cfg = gui->screen_config.config; + cfg.timestamp = getTick(); + logger.log(cfg); + + gui->screen_config.btn_start.setText("Starting..."); + + gui->screen_status.updateConfig(cfg); + + setupXbee(cfg); + startTransceiver(cfg); + + // Show status screen + gui->screen_manager.showScreen(XbeeGUI::SCREEN_STATUS); + } +} + +void onStopButtonClick(View* btn, Interaction action) +{ + if (action == Interaction::LONG_CLICK) + { + TextView* tv_btn = dynamic_cast<TextView*>(btn); + + if (tv_btn) + { + tv_btn->setText("Stopping..."); + } + + if (trans) + { + trans->stop(); + } + + if (energy_scanner.isRunning()) + { + energy_scanner.stop(); + } + + logger.stop(); + + gui->screen_manager.showScreen(XbeeGUI::SCREEN_END); + } +} + +void onMarkButtonClick(View* btn, Interaction action) +{ + UNUSED(btn); + if (action == Interaction::CLICK) + { + Mark m{getTick(), mark_counter++}; + logger.log(m); + + TextView* tv_btn = dynamic_cast<TextView*>(btn); + if (tv_btn) + { + tv_btn->setText("Mark Log (" + to_string(mark_counter) + ")"); + } + } +} + +void onEnergyButtonClick(View* btn, Interaction action) +{ + UNUSED(btn); + if (action == Interaction::CLICK) + { + energy_scanner.start(); + gui->screen_manager.showScreen(XbeeGUI::SCREEN_ENERGYSCAN); + } +} + +void startTransceiver(XbeeConfig config) +{ + snd_int.interval = config.send_interval; + trans = new XbeeTransceiver(*xbee, logger, snd_int, config.packet_size, + config.send_interval); + + if (!config.tx_enabled) + { + trans->disableSender(); + } + + trans->start(); +} + +void setupXbee(XbeeConfig config) +{ + if (config.data_rate_80k) + { + if (!Xbee::setDataRate(*xbee, true)) + { + gui->screen_status.tv_cfg_data_rate.setBackgroundColor(mxgui::red); + TRACE("[main] Error setting xbee data rate!\n"); + } + } + + if (!config.freq_hop) + { + if (!Xbee::disableFrequencyHopping(*xbee)) + { + gui->screen_status.tv_cfg_freq_hop.setBackgroundColor(mxgui::red); + TRACE("[main] Error disabling frequency hop!\n"); + } + } +} + +void configure() +{ + { + FastInterruptDisableLock dLock; + + // Enable SPI5 and TIM5 peripheral clocks + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + + // Set SPI pins to correct alternate mode + GpioSck::mode(Mode::ALTERNATE); + GpioMiso::mode(Mode::ALTERNATE); + GpioMosi::mode(Mode::ALTERNATE); + + GpioSck::alternateFunction(5); + GpioMiso::alternateFunction(5); + GpioMosi::alternateFunction(5); + + GpioATTN::mode(Mode::INPUT_PULL_UP); + + GpioLedLog::mode(Mode::OUTPUT); + GpioUserBtn::mode(Mode::INPUT_PULL_DOWN); + + // Set chip select pin to OUTPUT + GpioCS::mode(Mode::OUTPUT); + } + + // Chip select starts high (not asserted) + GpioCS::high(); + GpioLedLog::low(); + + // Enable rising-edge interrupt detection on PA2 + enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE); +} + +void __attribute__((used)) EXTI5_IRQHandlerImpl() +{ + if (xbee) + { + xbee->handleATTNInterrupt(); + } +} \ No newline at end of file diff --git a/src/tests/drivers/xbee/test-xbee-rcv.cpp b/src/tests/drivers/xbee/test-xbee-rcv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d542ddf699222d2dc37a3c335551d6d4f55db181 --- /dev/null +++ b/src/tests/drivers/xbee/test-xbee-rcv.cpp @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define RUN_SENDER false + +#include "test-xbee-bidir.cpp" \ No newline at end of file diff --git a/src/tests/drivers/xbee/test-xbee-snd.cpp b/src/tests/drivers/xbee/test-xbee-snd.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7c0f3508c7b2009887718720d51d151c895344b7 --- /dev/null +++ b/src/tests/drivers/xbee/test-xbee-snd.cpp @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta (luca.erbetta@skywarder.eu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define RUN_RECEIVER false + +#include "test-xbee-bidir.cpp" diff --git a/src/tests/test-hsm.cpp b/src/tests/test-hsm.cpp index 8495f86f8f0bf4404f82d76a3c1aa37545e1d2fb..2fa2687165f79f3cc3c27748d6626394d0923ea5 100644 --- a/src/tests/test-hsm.cpp +++ b/src/tests/test-hsm.cpp @@ -32,7 +32,7 @@ using namespace miosix; #define TOPIC_TEST 1 -#define CHECK_INIT() bool test_value = false +#define CHECK_INIT() bool test_value = false; (void)test_value #define CHECK_STATE(HSM, SIGNAL, STATE) do{ \ cout << "------------------------------" << endl; \