diff --git a/src/bacnet/basic/program/ubasic/README.md b/src/bacnet/basic/program/ubasic/README.md new file mode 100644 index 00000000..6d85ba24 --- /dev/null +++ b/src/bacnet/basic/program/ubasic/README.md @@ -0,0 +1,639 @@ +# uBASIC+BACnet + +uBasicPlus is an extension of the uBasic by Adam Dunkels (2006), +https://github.com/adamdunkels/ubasic, which includes +the strings and their functions from 'uBasic with strings' by David Mitchell (2008), +http://www.zenoshrdlu.com/kapstuff/zsubasic.html, +and some constructs and interpreter logic from 'CHDK', +http://chdk.wikia.com/wiki/CHDK_Scripting_Cross_Reference_Page. + +uBASIC+BACnet refactored https://github.com/mkostrun/UBASIC-PLUS core data +which enables multiple uBASIC programs to run at the same time, +refactored the hardware abstraction layer using a callback pattern +which enables some or all of the hardware abstraction implementations +for any hardware platform, and added some BACnet specific keywords +for creating, reading, and writing to BACnet objects and their properties. + +## uBASIC+BACnet Interpreter Features + +### Software and Flow Control + +- UBASIC, which can be a small non-interactive interpreter of + the programming language BASIC which offers a subset of the commands + that are part of the language, + 26 integer variables, named 'a' through 'z', + *if/then/else* with support for single symbol logical operators (>,<,=), + *for/next*, *let*, *goto*, *gosub*, *print* and '\n' (new line) as + end-of-line character. + +- uBasic with strings, which adds support for strings: + 26 string variables, named a$-z$, string literals, variables and expressions can + be used in assignments and print statements string expressions can be compared + for equality in if statements string expressions can be concatenated using '+', + and a number of string functions: *left$*, *mid$*, *right*, *str$*, *chr$*, + *val*, *len$*, *instr$*, *asc*. + +- UBASIC-PLUS string scratch space (SSS) which all string variables and + intermediate results are stored using a structure header (1byte) + data + (strlen+1 bytes). At the end of each statement the SSS is cleared of all + non-assigned strings. This means that rather than pointers (size 4 bytes) + the addresses in the space are used (2 bytes). This caused all string + functions to be rewritten. Most importantly, the garbage collection is + done in place rather then by temporarily doubling the storage space. + The garbage collection now used is similar to the garbage collection done + for managing arrays. + +- Elements of CHDK + + The line numbering is removed and replaced by the labels CHDK-style + + ``` + :label1 + .... + goto label1; + ``` + + When these labels are referred to in goto/gosub this is done without + quotation marks (unlike CHDK). Furthermore, in labels '_' can be used. + Not even internally the lines are numbered (like it was the case with CHDK). + Rather, pointers are used to script string, meaning that the + returns - from gosub, for/next, if/then/else, while/endwhile - are + faster as they do not require searches (over line numbers). + +- Strings can be encompassed by single or double quotations. + Tokenizer can identify labels. + +- End of line characters *'\n'* and *';'* + +- *config.h* + + Allows user to select which of the features below (if any) they want in + their uBasic build. + +- Fixep point floats are implemented through Fixed Point Math Library for C +by Ivan Voras and Tim Hartnick, https://sourceforge.net/projects/fixedptc. +The library is enhanced with str_fixedpt function, which converts a string +to fixed point float. + +- flow control + - logical operators supported (<>,<=,>=,==,&&,||,!) + - complex logical expressions supported with use of brackets + - multi-line If/then/else/endif-command (CHDK-style) + - while/endwhile + +- *input {a,a$,a@(i)}, timeout_ms* + + Wait at most until *timeout_ms* for input from the serial port. Wait is not + executed inside the interpreter. Relies on external functions for + serial-available and serial-read to be supplied as documented in *config.h* + file. + +- 26 fixed point arrays, a@ to z@, dynamically allocated using DIM command, + ``` + dim a@(5); + for i = 1 to 5; + input a@(i),10000; + next i; + ``` + In this example code expects 5 array entries to be entered through serial + port, and waits at most 10sec for each input to complete. + In the scripts or from the input line the numbers can be entered either directly + as hex values, + ``` + ... x = 0xaabbccdd + ``` + or as binary values, + ``` + ... x = 0b1101001 + ``` + or as decimal values, + ``` + ... x = 123456[d,D,L,l] + ``` + if the default fixed point float is not desired. + +- *println* + + Same as *print* but adds an empty line at the end. Additional identifiers + *hex* or *dec* can be used to print the number as (hex)adecimal if fixed + point floats are not desired, as in this example: + ``` + ... println [hex,dec] x + ``` + +- *sleep(f)* + + Wait *f* seconds, which can be fixed point float. This is not executed + inside the BASIC interpreter. + +- *ran, uniform* + + System random number generators based on the external function as documented + in *config.h*. The implementation could use multiple calls to a random number + generator to acquire the required number of random bits. + + - *ran* - generates random positive integer in fixed point float + representation. + + - *uniform* - generates random fixed point float in the range 0 to 0.999. + + +- *tic(n), a=toc(n)* + + rlabplus-type timers for measuring the passed time from different + breakpoints in the script + +- *sqrt, sin, cos, tan, exp, ln, pow* + + fixed point arithmetic single argument functions from the fixed point math library. + +- *floor, ceil, round, abs* + + fixed point float to fixed point integer arithmetic functions. + +- *avgw* + + Returns a weighted moving average for a given a latest value, the + previous value, and the number of samples value. + +- *clear* + + Clears all variables, arrays and strings. It is good practice to put it as the +first line of the script. + + +### Hardware Control + + +- *pinmode(pin,mode,speed), a=dread(pin), dwrite(pin,state)* + + Allows direct control over digital pins. The pin, mode, speed, and state + designations accept an 8-bit value passed to the the hardware abstraction. + +- *awrite_conf(prescaler,period), awrite(channel,value), awrite(channel)* + + direct control over output pins that support analog outputs on the + micro-controller that are passed to the hardware abstraction. + *prescaler* and *period* are 16-bit values. + *channel* is an 8-bit value, and *value* is a signed 32-bits integer. + +- *i = flag(channel)* + + Allow flags that can be set outside BASIC interpreter, + e.g., using interrupts, to be used in flow control. + The *channel* is an 8-bit value, and the return is a signed 8-bit integer. + ``` + :waithere + if (flag(1)==0) then goto waithere; + ``` + In this example the script sits in this loop until the hardware event + flag no. 1 gets set by external process. Importantly, after the interpreter + recognizes that the flag has been set, it immediately resets it so the + subsequent calls will return 0 until the flag is set again externally. + + Importantly, while waiting for the flag, the microcontrolled does not sit + inside the interpreter. + +- *aread_conf(duration,nreads), i = aread(channel)* + + Configure and read input from analog inputs of the microcontroller. + The *channel* is an 8-bit value, and the return is a signed 32-bit integer. + + - duration: + + 8-bit sample time passed to the hardware abstraction. + + - nreads: 8-bit value passed to the hardware abstraction which can + be used to dictate the number of sample readings to average + +- *store(x[x$,x@]), i=recall(x[x$,x@])* + + Store and recall variable, string or array. In that way variables can survive + reboot of the device. + +- *bac_create(type, instance, name)* + + Create a BACnet object of *type* and *instance* where + *type* is a number matching the BACnetObjectType enumeration + from the BACnet Standard (0=analog input, 1=analog output, 2=analog value, + 3=binary input, 4=binary-output, 5=binary-value, etc). + The *instance* can be any value from 0 to 4194302. + The *name* is a string that is assigned to the object-name property of the + BACnet object. + + ``` + bac_create(0, 1, 'ADC-1-AVG') + bac_create(0, 2, 'ADC-2-AVG') + bac_create(2, 1, 'ADC-1-RAW') + bac_create(2, 2, 'ADC-2-RAW') + bac_create(4, 1, 'LED-1') + ``` + +- *i = bac_read(type, instance, property)* + + Read a numeric *property* value from the object *type* and *instance*. + The *property* is a number matching the BACnetPropertyIdentifier + enumeration from the BACnet standard (85=present-value). + *type* is a number matching the BACnetObjectType enumeration + from the BACnet Standard (0=analog input, etc). + The *instance* can be any value from 0 to 4194302. + The returned value is a variable type. + + ``` + a = 0 + y = bac_read(2, 1, 85) + a = avgw(y, a, 10) + bac_write(0, 1, 85, a) + ``` + +- *bac_write(type, instance, property, value)* + + Write a variable *property* *value* to the object *type* and *instance*. + The *property* is a number matching the BACnetPropertyIdentifier + enumeration from the BACnet standard (85=present-value). + *type* is a number matching the BACnetObjectType enumeration + from the BACnet Standard (0=analog input, etc). + The *instance* can be any value from 0 to 4194302. + The *value* is a variable type. + + ``` + a = aread(1) + bac_write(2, 1, 85, floor(a)) + ``` + +## uBASIC+BACnet SCRIPT EXAMPLES + +### Demo 1 - warm up +``` +println 'Demo 1 - Warm-up' +gosub l1 +for i = 1 to 2 + for j = 1 to 2 + println 'i,j=',i,j + next j +next i +println 'Demo 1 Completed' +end + :l1 +println 'subroutine' +return +``` + +### Demo 2 - 'uBasic with strings' by David Mitchell +``` +println 'Demo 2' +a$= 'abcdefghi' +b$='123456789' +println 'Length of a$=', len(a$) +println 'Length of b$=', len(b$) +if len(a$) = len(b$) then println 'same length' +if a$ = b$ then println 'same string' +c$=left$(a$+ b$,12) +println c$ +c$=right$(a$+b$, 12) +println c$ +c$=mid$(a$+b$, 8,8) +println c$ +c$=str$(13+42) +println c$ +println len(c$) +println len('this' + 'that') +c$ = chr$(34) +println 'c$=' c$ +j = asc(c$) +println 'j=' j +println val('12345') +i=instr(3, '123456789', '67') +println 'position of 67 in 123456789 is', i +println mid$(a$,2,2)+'xyx' +println 'Demo 2 Completed' +end +``` + +### Demo 3 - uBasic-Plus is here +``` +println 'Demo 3 - Plus' +tic(1) +for i = 1 to 2 + j = i + 0.25 + 1/2 + println 'j=' j + k = sqrt(2*j) + ln(4*i) + cos(i+j) + sin(j) + println 'k=' k +next i +:repeat + if toc(1)<=300 then goto repeat +for i = 1 to 2 +println 'ran(' i ')=' ran +next i +for i = 1 to 2 +println 'uniform(' i ')=' uniform +next i +for i = 1 to 2 +x = 10 * uniform +println 'x=' x +println 'floor(x)=' floor(x) +println 'ceil(x)=' ceil(x) +println 'round(x)=' round(x) +println 'x^3=' pow(x,3) +next i +println 'Digital Write Test' +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 +println 'Press the Blue Button or type kill!' +:presswait + if flag(1)=0 then goto presswait +tic(1) +println 'Blue Button pressed!' +:deprwait + if flag(2)=0 then goto deprwait +println 'duration =' toc(1) +println 'Blue Button de-pressed!' +println 'Demo 3 Completed' +end +``` + +### Demo 4 - input array entries in 10 sec time +``` +println 'Demo 4 - Input with timeouts' +dim a@(5) +for i = 1 to 5 + print '?' + input a@(i),10000 +next i +println 'end of input' +for i = 1 to 5 + println 'a(' i ') = ' a@(i) +next i +println 'Demo 4 Completed' +end +``` + +### Demo 5 - analog read with arrays +``` +println 'Demo 5 - analog inputs and arrays'; +for i = 1 to 100; + x = aread(16); + y = aread(17); + println 'VREF,TEMP=', x, y; +next i; +for i = 1 to 1; + n = floor(10 * uniform) + 2 ; + dim b@(n); + for j = 1 to n; + b@(j) = ran; + println 'b@(' j ')=' b@(j); + next j; +next i; +println 'Demo 5 Completed'; +end; +``` + +### Demo 6 - if/then/else/endif and while/endwhile +``` +println 'Demo 6: Multiline if, while' +println 'Test If: 1' +for i=1 to 10 step 0.125 + x = uniform + if (x>=0.5) then + println x, 'is greater then 0.5' + else + println x, 'is smaller then 0.5' + endif + println 'i=' i +next i +println 'End of If-test 1' +println 'Test While: 1' +i=10 +while ((i>=0)&&(uniform<=0.9)) + i = i - 0.125 + println 'i =', i +endwhile +println 'End of While-test 1' +println 'Demo 6 Completed' +end +``` + +### Demo 7 - Analog Read Forever Loop +``` +println 'Demo 7: Analog Read Forever Loop' +y=0 +:startover + x = aread(10) + if (abs(x-y)>20) then + y = x + println 'x=',x + endif + sleep (0.2) + goto startover +end +``` + + +### Demo 8 - Analog Output Test +``` +println 'Demo 8: analog write 4-Channel Test' +p = 65536 +for k = 1 to 10 + p = p/2 + awrite_conf(p,4096) + println 'prescaler = ' p + for i = 1 to 10 + for j = 1 to 4 + awrite(j,4095*uniform) + next j + println ' analog write = ' awrite(1),awrite(2),awrite(3),awrite(4) + sleep(5) + next i +next k +awrite(1,0) +awrite(2,0) +awrite(3,0) +awrite(4,0) +end +``` + +### Demo 9 - Store/recall in non-volatile memory Test +``` +clear +println 'Demo 9: store/recall to non-volatile memory' +if (recall(x)==0) then + println 'generating x' + x = uniform + store(x) +endif +println 'stored: x=' x +if (recall(y@)==0) then + println 'generating y' + dim y@(10) + for i=1 to 10 + y@(i) = uniform + next i + store(y@) +endif +println 'stored: y@' +for i=1 to 10 + println ' y@('i')=' y@(i) +next i +if (recall(s$)==0) then + println 'generating s' + s$='what is going on?' + store(s$) +endif +println 'stored: s$',s$ +println 'Demo 9 Completed' +println 'Please run it once more' +end +``` + +### Demo 10 - BACnet & GPIO +``` +println 'Demo - BACnet & GPIO' +bac_create(0, 1, 'ADC-1-AVG') +bac_create(0, 2, 'ADC-2-AVG') +bac_create(2, 1, 'ADC-1-RAW') +bac_create(2, 2, 'ADC-2-RAW') +bac_create(4, 1, 'LED-1') +bac_create(2, 3, 'Counter 1') +bac_create(2, 4, 'Counter 2') +bac_create(2, 5, 'Counter 1-FLOOR') +bac_create(2, 6, 'Counter 1-CEIL') +bac_create(2, 7, 'Counter 1-ROUND') +bac_create(2, 8, 'Counter 1-POW3') +y = 0.1 +z = 0.0 +bac_write(2, 3, 85, y) +bac_write(2, 4, 85, z) +:startover + y = bac_read(2, 3, 85) + y = y + 1.1 + if (y > 100.0) then + y = 0.0 + z = z + 1.0 + endif + if (z > 1000.0) then + z = 0.0 + endif + bac_write(2, 3, 85, y) + bac_write(2, 4, 85, z) + bac_write(2, 5, 85, floor(y)) + bac_write(2, 6, 85, ceil(y)) + bac_write(2, 7, 85, round(y)) + bac_write(2, 8, 85, pow(y, 3)) + a = aread(1) + bac_write(2, 1, 85, a) + c = avgw(a, c, 10) + bac_write(0, 1, 85, c) + b = aread(2) + bac_write(2, 1, 85, b) + d = avgw(b, d, 10) + bac_write(0, 2, 85, d) + h = bac_read(4, 1, 85) + dwrite(1, (h % 2)) + sleep (0.2) +goto startover +end +``` + +## uBASIC+BACnet HARDWARE ABSTRACTION LAYER + +The uBASIC+BACnet hardware abstraction layer is integrated using +a set of callback functions assigned to each `struct ubasic_data` +defined in *ubasic/ubasic.h* file. The structure members depend +on *ubasic/config.h* file defines so that the abstraction can be +as small or large as the implementation allows or requires. + +``` + /* API for hardware drivers */ + #if defined(UBASIC_SCRIPT_HAVE_PWM_CHANNELS) + void (*pwm_config)(uint16_t psc, uint16_t per); + void (*pwm_write)(uint8_t ch, int32_t dutycycle); + int32_t (*pwm_read)(uint8_t ch); + #endif + #if defined(UBASIC_SCRIPT_HAVE_GPIO_CHANNELS) + void (*gpio_config)(uint8_t ch, int8_t mode, uint8_t freq); + void (*gpio_write)(uint8_t ch, uint8_t pin_state); + int32_t (*gpio_read)(uint8_t ch); + #endif + #if ( \ + defined(UBASIC_SCRIPT_HAVE_TICTOC_CHANNELS) || \ + defined(UBASIC_SCRIPT_HAVE_SLEEP) || \ + defined(UBASIC_SCRIPT_HAVE_INPUT_FROM_SERIAL)) + uint32_t (*mstimer_now)(void); + #endif + #if defined(UBASIC_SCRIPT_HAVE_ANALOG_READ) + void (*adc_config)(uint8_t sampletime, uint8_t nreads); + int32_t (*adc_read)(uint8_t channel); + #endif + #if defined(UBASIC_SCRIPT_HAVE_HARDWARE_EVENTS) + int8_t (*hw_event)(uint8_t bit); + void (*hw_event_clear)(uint8_t bit); + #endif + #if defined(UBASIC_SCRIPT_HAVE_RANDOM_NUMBER_GENERATOR) + uint32_t (*random_uint32)(uint8_t size); + #endif + #if defined(UBASIC_SCRIPT_HAVE_STORE_VARS_IN_FLASH) + void (*variable_write)( + uint8_t Name, uint8_t Vartype, uint8_t datalen_bytes, uint8_t *dataptr); + void (*variable_read)( + uint8_t Name, uint8_t Vartype, uint8_t *dataptr, uint8_t *datalen); + #endif + #if defined(UBASIC_SCRIPT_HAVE_INPUT_FROM_SERIAL) + int (*ubasic_getc)(void); + #endif + #if defined(UBASIC_SCRIPT_HAVE_PRINT_TO_SERIAL) + void (*serial_write)(const char *buffer, uint16_t n); + #endif + #if defined(UBASIC_SCRIPT_HAVE_BACNET) + void (*bacnet_create_object)( + uint16_t object_type, uint32_t instance, char *object_name); + void (*bacnet_write_property)( + uint16_t object_type, + uint32_t instance, + uint32_t property_id, + UBASIC_VARIABLE_TYPE value); + UBASIC_VARIABLE_TYPE(*bacnet_read_property) + (uint16_t object_type, uint32_t instance, uint32_t property_id); + #endif +``` + +## uBASIC+BACnet APPLICATION INTEGRATION + +The uBASIC+BACnet comprise of six files config.h, fixedptc.h, tokenizer.c, +tokenizer.h, ubasic.c and ubasic.h. + +An example implementation of the hardware related functions (random +number generation, gpio, hardware events, sleep and tic/toc) and usage +of the applicatlion layer functions can be found in the `ports/stm32f4xx` +project. + +- `ports/stm32f4xx/program-ubasic.c` + + A BACnet Program object that handles Load, Start, Run, Halt, Restart, + and Unload values from the BACnetProgramRequest enumeration options + integrated with the uBASIC+BACnet API: + - `ubasic_port_init()` + - `ubasic_clear_variables()` + - `ubasic_load_program()` + - `ubasic_halt_program()` + - `ubasic_run_program()` + + Additionally the Program object provides a glimpse inside the + uBASIC+BACnet script using `ubasic_program_location()` function. + +- `ports/stm32f4xx/ubasic-port.c` + + A port of the hardware layer to STM32F4xx board and the BACnet API functions + +- `ports/stm32f4xx/main.c` + + The hardware setup and example uBASIC+BACnet programs running on the + hardware as BACnet File objects used by BACnet Program objects.