/** * @file * @details Test the uBASIC implementation * @author Steve Karg * @date 2025 * @brief Platform libc and compiler abstraction layer * @copyright SPDX-License-Identifier: MIT */ #include #include #include #include /** * @addtogroup bacnet_tests * @{ */ static uint32_t Tick_Counter = 0; static void tick_increment(void) { Tick_Counter++; } static uint32_t tick_now(void) { return Tick_Counter; } static int32_t ADC_Value[UINT8_MAX + 1]; static int32_t adc_read(uint8_t channel) { return ADC_Value[channel]; } static void adc_config(uint8_t sampletime, uint8_t nreads) { (void)sampletime; (void)nreads; } static uint32_t Event_Mask; static int8_t hw_event(uint8_t bit) { if (bit < 32) { if (Event_Mask & (1UL << bit)) { return 1; // Event is set } } return 0; // Event is not set } static void hw_event_clear(uint8_t bit) { if (bit < 32) { Event_Mask &= ~(1UL << bit); } } static uint8_t GPIO_Pin_State[UINT8_MAX + 1]; static void gpio_write(uint8_t ch, uint8_t pin_state) { GPIO_Pin_State[ch] = pin_state; } static void gpio_config(uint8_t ch, int8_t mode, uint8_t freq) { (void)ch; (void)mode; (void)freq; } static void pwm_config(uint16_t psc, uint16_t per) { (void)psc; (void)per; } static int32_t Duty_Cycle[UINT8_MAX + 1]; static void pwm_write(uint8_t ch, int32_t dutycycle) { Duty_Cycle[ch] = dutycycle; } static int32_t pwm_read(uint8_t ch) { return Duty_Cycle[ch]; } /** * @brief Write a buffer to the serial port * @param msg Pointer to the buffer to write * @param n Number of bytes to write */ static void serial_write(const char *msg, uint16_t n) { printf("%.*s", n, msg); fflush(stdout); } /** * @brief Generate a random number * @param size Size of the random number in bits * @return Random number size-bits wide */ static uint32_t random_uint32(uint8_t size) { uint32_t r = 0; for (int i = 0; i < size; i++) { r |= (1 << i); } return r; } struct test_bacnet_object { uint16_t object_type; uint32_t object_instance; uint32_t property_id; UBASIC_VARIABLE_TYPE property_value; char *object_name; }; static struct test_bacnet_object Test_BACnet_Object[5]; static void bacnet_create_object(uint16_t object_type, uint32_t instance, char *object_name) { if (instance < ARRAY_SIZE(Test_BACnet_Object)) { Test_BACnet_Object[instance].object_type = object_type; Test_BACnet_Object[instance].object_instance = instance; Test_BACnet_Object[instance].object_name = strdup(object_name); } } static void bacnet_write_property( uint16_t object_type, uint32_t instance, uint32_t property_id, UBASIC_VARIABLE_TYPE value) { if (instance < ARRAY_SIZE(Test_BACnet_Object)) { Test_BACnet_Object[instance].object_type = object_type; Test_BACnet_Object[instance].object_instance = instance; Test_BACnet_Object[instance].property_id = property_id; Test_BACnet_Object[instance].property_value = value; } } static UBASIC_VARIABLE_TYPE bacnet_read_property( uint16_t object_type, uint32_t instance, uint32_t property_id) { if (instance < ARRAY_SIZE(Test_BACnet_Object)) { if (Test_BACnet_Object[instance].object_type == object_type && Test_BACnet_Object[instance].property_id == property_id) { return Test_BACnet_Object[instance].property_value; } } return 0; } /** * @brief Test */ #if defined(CONFIG_ZTEST_NEW_API) ZTEST(ubasic_tests, test_ubasic_gpio) #else static void test_ubasic_gpio(void) #endif { struct ubasic_data data = { 0 }; const char *program = /* program listing with either \0, \n, or ';' at the end of each line. note: indentation is not required */ "println 'Demo - GPIO & ADC';" "pinmode(0xc0,-1,0);" "pinmode(0xc1,-1,0);" "pinmode(0xc2,-1,0);" "pinmode(0xc3,-1,0);" "for j = 0 to 2;" " dwrite(0xc0,(j % 2));" " dwrite(0xc1,(j % 2));" " dwrite(0xc2,(j % 2));" " dwrite(0xc3,(j % 2));" " sleep(0.5);" "next j;" "aread_conf(7,16);" "aread_conf(7,17);" "a = 4096 / 2;" "z = 4096 / 2;" "s = 5;" "for i = 1 to s;" " x = aread(16);" " y = aread(17);" " println 'VREF,TEMP=', x, y;" " a = avgw(x,a,s);" " z = avgw(y,z,s);" "next i;" "println 'average x y=', a, z;" "end;"; UBASIC_VARIABLE_TYPE value = 0; data.mstimer_now = tick_now; data.serial_write = serial_write; data.gpio_config = gpio_config; data.gpio_write = gpio_write; data.adc_config = adc_config; data.adc_read = adc_read; data.pwm_config = pwm_config; data.pwm_write = pwm_write; data.pwm_read = pwm_read; data.hw_event = hw_event; data.hw_event_clear = hw_event_clear; ADC_Value[16] = 2048; ADC_Value[17] = 2048; ubasic_load_program(&data, program); zassert_equal(data.status.bit.isRunning, 1, NULL); zassert_equal(data.status.bit.Error, 0, NULL); while (!ubasic_finished(&data)) { ubasic_run_program(&data); tick_increment(); } zassert_equal(data.status.bit.Error, 0, NULL); /* check the final value of the bacnet read property */ value = ubasic_get_variable(&data, 'a'); zassert_equal( fixedpt_toint(value), 2048, "a value=%d", fixedpt_toint(value)); value = ubasic_get_variable(&data, 'z'); zassert_equal( fixedpt_toint(value), 2048, "z value=%d", fixedpt_toint(value)); } /** * @brief Test */ #if defined(CONFIG_ZTEST_NEW_API) ZTEST(ubasic_tests, test_ubasic_bacnet) #else static void test_ubasic_bacnet(void) #endif { struct ubasic_data data = { 0 }; const char *program = /* program listing with either \0, \n, or ';' at the end of each line. note: indentation is not required */ "println 'Demo - BACnet';" "bac_create(0, 1, 'Object1');" "bac_create(0, 2, 'Object2');" "bac_create(0, 3, 'Object3');" "bac_create(0, 4, 'Object4');" "bac_write(0, 1, 85, 42);" "a = bac_read(0, 1, 85);" "println 'bac_read 0, 1, 85 = ' a;" "end;"; UBASIC_VARIABLE_TYPE value = 0; data.mstimer_now = tick_now; data.serial_write = serial_write; data.bacnet_create_object = bacnet_create_object; data.bacnet_write_property = bacnet_write_property; data.bacnet_read_property = bacnet_read_property; ubasic_load_program(&data, program); zassert_equal(data.status.bit.isRunning, 1, NULL); zassert_equal(data.status.bit.Error, 0, NULL); while (!ubasic_finished(&data)) { ubasic_run_program(&data); tick_increment(); } zassert_equal(data.status.bit.Error, 0, NULL); /* check the final value of the bacnet read property */ value = ubasic_get_variable(&data, 'a'); zassert_equal( fixedpt_toint(value), 42, "bacnet read property value=%d", fixedpt_toint(value)); zassert_equal( Test_BACnet_Object[1].object_type, 0, "bacnet object type=%d", Test_BACnet_Object[1].object_type); zassert_equal( Test_BACnet_Object[1].object_instance, 1, "bacnet object instance=%d", Test_BACnet_Object[1].object_instance); zassert_equal( Test_BACnet_Object[1].property_id, 85, "bacnet object property ID=%d", Test_BACnet_Object[1].property_id); zassert_equal( strcmp(Test_BACnet_Object[1].object_name, "Object1"), 0, "bacnet object name=%s", Test_BACnet_Object[1].object_name); zassert_equal( strcmp(Test_BACnet_Object[2].object_name, "Object2"), 0, "bacnet object name=%s", Test_BACnet_Object[2].object_name); zassert_equal( strcmp(Test_BACnet_Object[3].object_name, "Object3"), 0, "bacnet object name=%s", Test_BACnet_Object[3].object_name); } #if defined(CONFIG_ZTEST_NEW_API) ZTEST(ubasic_tests, test_ubasic_math) #else static void test_ubasic_math(void) #endif { struct ubasic_data data = { 0 }; const char *program = /* program listing with either \0, \n, or ';' at the end of each line. note: indentation is not required */ "println 'Demo - Math';" "for i = 1 to 2;" " j = i + 0.25 + 1/2;" " k = sqrt(2*j) + ln(4*i) + cos(i+j) + sin(j);" "next i;" "println 'j=' j;" "println 'k=' k;" "dim r@(5);" "for i = 1 to 5;" " r@(i) = ran;" " println 'r[' i ']=' r@(i);" "next i;" "dim u@(5);" "a = 0;" "for i = 1 to 5;" " u = uniform;" " println 'u[' i ']=' u;" " u@(i) = u;" " a = avgw(u,a,5);" "next i;" "println 'uniform moving average = ' a;" "x = 1000 * uniform;" "f = floor(x);" "c = ceil(x);" "r = round(x);" "w = pow(x,3);" "println 'x=' x;" "println 'floor(x)=' f;" "println 'ceil(x)=' c;" "println 'round(x)=' r;" "println 'x^3=' w;" "end;"; UBASIC_VARIABLE_TYPE value = 0, xvalue = 0, arrayvalue[5]; unsigned i; int32_t value_int = 0; int32_t value_frac = 0; int32_t value_math = 0; data.mstimer_now = tick_now; data.serial_write = serial_write; data.random_uint32 = random_uint32; ubasic_load_program(&data, program); zassert_equal(data.status.bit.isRunning, 1, NULL); zassert_equal(data.status.bit.Error, 0, NULL); while (!ubasic_finished(&data)) { ubasic_run_program(&data); tick_increment(); } zassert_equal(data.status.bit.Error, 0, NULL); /* check the final values of the math operations */ value = ubasic_get_variable(&data, 'j'); value_int = fixedpt_toint(value); value_frac = fixedpt_fracpart_floor_toint(value, 2); zassert_equal(value_int, 2, "int=%d", value_int); zassert_equal(value_frac, 75, "frac=%d", value_frac); value = ubasic_get_variable(&data, 'k'); value_int = fixedpt_toint(value); value_frac = fixedpt_fracpart_floor_toint(value, 2); zassert_equal(value_int, 4, "int=%d", value_int); zassert_equal(value_frac, 83, "frac=%d", value_frac); for (i = 0; i < ARRAY_SIZE(arrayvalue); i++) { arrayvalue[i] = ubasic_get_arrayvariable(&data, 'r', 1 + i); value_int = fixedpt_toint(arrayvalue[i]); zassert_equal(value_int, 1, "ran[%u]=%d", 1 + i, value_int); } /* uniform random value */ xvalue = ubasic_get_variable(&data, 'x'); /* floor */ value_math = fixedpt_floor_toint(xvalue); value = ubasic_get_variable(&data, 'f'); value_int = fixedpt_toint(value); zassert_equal( value_int, value_math, "int=%d floor=%d", value_int, value_math); /* ceiling */ value_math = fixedpt_ceil_toint(xvalue); value = ubasic_get_variable(&data, 'c'); value_int = fixedpt_toint(value); zassert_equal( value_int, value_math, "int=%d ceil=%d", value_int, value_math); /* round */ value_math = fixedpt_round_toint(xvalue); value = ubasic_get_variable(&data, 'r'); value_int = fixedpt_toint(value); zassert_equal( value_int, value_math, "int=%d round=%d", value_int, value_math); return; } /** * @brief Verify uBASIC string variable handling. * @details Ensures that string variables are truncated at UBASIC_STRINGLEN_MAX * and remain correctly null-terminated when assigned and read back. */ #if defined(CONFIG_ZTEST_NEW_API) ZTEST(ubasic_tests, test_ubasic_strings) #else static void test_ubasic_strings(void) #endif { struct ubasic_data data = { 0 }; const char *program1 = /* program listing with either \0, \n, or ';' at the end of each line. note: indentation is not required */ "println 'Demo - Strings'\n" "let a$ = \"1234567890123456789012345678901234567890X\"\n" "print a$\n"; const char *program2 = /* program listing with either \0, \n, or ';' at the end of each line. note: indentation is not required */ "println 'Demo - Strings'\n" "let z$ = \"1\"\n" "print z$\n"; const char *tmpstring; ubasic_load_program(&data, program1); zassert_equal(data.status.bit.isRunning, 1, NULL); zassert_equal(data.status.bit.Error, 0, NULL); while (!ubasic_finished(&data)) { ubasic_run_program(&data); } zassert_equal(data.status.bit.Error, 0, NULL); tmpstring = ubasic_ptr_stringvariable(&data, 'a'); zassert_not_null(tmpstring, "string variable not found"); zassert_equal( strlen(tmpstring), UBASIC_STRINGLEN_MAX - 1, "string length=%d", (int)strlen(tmpstring)); zassert_equal( strcmp(tmpstring, "123456789012345678901234567890123456789"), 0, "string value=%s", tmpstring); ubasic_load_program(&data, program2); zassert_equal(data.status.bit.isRunning, 1, NULL); zassert_equal(data.status.bit.Error, 0, NULL); while (!ubasic_finished(&data)) { ubasic_run_program(&data); } zassert_equal(data.status.bit.Error, 0, NULL); tmpstring = ubasic_ptr_stringvariable(&data, 'z'); zassert_not_null(tmpstring, "string variable not found"); zassert_equal( strlen(tmpstring), 1, "string length=%d", (int)strlen(tmpstring)); zassert_equal(strcmp(tmpstring, "1"), 0, "string value=%s", tmpstring); } /** * @brief Test */ #if defined(CONFIG_ZTEST_NEW_API) ZTEST(ubasic_tests, test_ubasic) #else static void test_ubasic(void) #endif { struct ubasic_data data = { 0 }; const char *program = /* program listing with either \0, \n, or ';' at the end of each line. note: indentation is not required */ "println 'Demo - Flow';" "gosub l1;" "for i = 1 to 8;" " for j = 1 to 9;" " println 'i,j=',i,j;" " next j;" "next i;" "println 'Demo 1 Completed';" "end;" ":l1 " " println 'subroutine';" "return;"; UBASIC_VARIABLE_TYPE value = 0; int32_t value_int = 0; ubasic_load_program(&data, program); zassert_equal(data.status.bit.isRunning, 1, NULL); zassert_equal(data.status.bit.Error, 0, NULL); while (!ubasic_finished(&data)) { ubasic_run_program(&data); } zassert_equal(data.status.bit.Error, 0, NULL); /* check the final value of i and j */ value = ubasic_get_variable(&data, 'i'); value_int = fixedpt_toint(value); zassert_equal(value_int, 9, NULL); value = ubasic_get_variable(&data, 'j'); value_int = fixedpt_toint(value); zassert_equal(value_int, 10, NULL); return; } /** * @} */ #if defined(CONFIG_ZTEST_NEW_API) ZTEST_SUITE(ubasic_tests, NULL, NULL, NULL, NULL, NULL); #else void test_main(void) { ztest_test_suite( ubasic_tests, ztest_unit_test(test_ubasic), ztest_unit_test(test_ubasic_strings), ztest_unit_test(test_ubasic_math), ztest_unit_test(test_ubasic_bacnet), ztest_unit_test(test_ubasic_gpio)); ztest_run_test_suite(ubasic_tests); } #endif