From 5b5b9fbfecc6b60c7a1ec459d7a17fa70754296d Mon Sep 17 00:00:00 2001 From: ankraft Date: Fri, 10 May 2024 14:30:01 +0200 Subject: [PATCH] More documentation --- docs/ACMEScript-functions.md | 4 +- docs/docs/development/ACMEScript-functions.md | 1006 ++++++++--------- docs/docs/development/ACMEScript-metatags.md | 171 ++- .../docs/development/ACMEScript-operations.md | 45 +- .../development/ACMEScript-uppertester.md | 9 +- docs/docs/development/ACMEScript-variables.md | 67 +- docs/docs/development/ACMEScript.md | 86 +- docs/docs/development/AttributePolicies.md | 263 +++++ docs/docs/development/DebugMode.md | 12 + .../docs/development/FlexContainerPolicies.md | 127 +++ docs/docs/development/HelpDocumentation.md | 31 + docs/docs/development/Integrating_ACME.md | 46 + docs/docs/development/NotificationServer.md | 61 + docs/docs/development/Overview.md | 64 -- docs/docs/development/StartupResources.md | 56 + docs/docs/development/TypeChecking.md | 11 + docs/docs/development/UnitTests.md | 234 ++-- docs/docs/help/Contributing.md | 21 +- docs/docs/help/FAQ.md | 10 +- docs/docs/home/Acknowledgements.md | 13 + docs/docs/home/License.md | 1 - docs/docs/home/Supported.md | 12 +- docs/docs/howtos/Docker.md | 42 +- .../howtos/ExperimentalWebSocketBinding.md | 39 + docs/docs/howtos/ExportResources.md | 75 ++ docs/docs/howtos/HowTo-pyenv.md | 51 +- docs/docs/howtos/HowTos.md | 3 + docs/docs/howtos/RaspberryPi.md | 33 +- docs/docs/howtos/StandAloneWebUI.md | 47 + docs/docs/images/export_resource.png | Bin 0 -> 111758 bytes docs/docs/index.md | 4 +- docs/docs/neu/Importing.md | 391 ------- docs/docs/setup/Configuration-basic.md | 36 +- docs/docs/setup/Configuration-introduction.md | 18 +- docs/docs/setup/Console.md | 9 +- docs/docs/setup/Database.md | 35 +- docs/docs/setup/Installation.md | 30 +- docs/docs/setup/Operation-diagrams.md | 24 +- docs/docs/setup/Operation-uppertester.md | 61 +- docs/docs/setup/Running.md | 34 +- docs/docs/setup/WebUI.md | 49 - docs/mkdocs.yml | 17 +- 42 files changed, 1741 insertions(+), 1607 deletions(-) create mode 100644 docs/docs/development/AttributePolicies.md create mode 100644 docs/docs/development/DebugMode.md create mode 100644 docs/docs/development/FlexContainerPolicies.md create mode 100644 docs/docs/development/HelpDocumentation.md create mode 100644 docs/docs/development/Integrating_ACME.md create mode 100644 docs/docs/development/NotificationServer.md create mode 100644 docs/docs/development/StartupResources.md create mode 100644 docs/docs/development/TypeChecking.md create mode 100644 docs/docs/home/Acknowledgements.md create mode 100644 docs/docs/howtos/ExperimentalWebSocketBinding.md create mode 100644 docs/docs/howtos/ExportResources.md create mode 100644 docs/docs/howtos/HowTos.md create mode 100644 docs/docs/howtos/StandAloneWebUI.md create mode 100644 docs/docs/images/export_resource.png delete mode 100644 docs/docs/neu/Importing.md diff --git a/docs/ACMEScript-functions.md b/docs/ACMEScript-functions.md index 3efb6502..1a769f79 100644 --- a/docs/ACMEScript-functions.md +++ b/docs/ACMEScript-functions.md @@ -2112,7 +2112,7 @@ Examples: The `import-raw` function creates a resource in the CSE without using the normal procedures when handling a [CREATE request](#create-resource). The resource is added to the resource tree without much validation. -This function is primarily used when importing initial resources, and when restoring resources during the [startup](ACMEScript-metatags.md#meta_init) of the CSE. +This function is primarily used when importing initial resources, and when restoring resources during the [startup](ACMEScript-metatags.md#init) of the CSE. `resource` is a valid oneM2M resource. All necessary attributes must be present in that resource, including the *parentID* ( *pi* ) attribute that determines the location in the resource tree. @@ -2654,7 +2654,7 @@ Evaluates to *true* if the script was started as an "autorun" script. This is th in a script. -See also: [@tuiAutoRun](ACMEScript-metatags.md#meta_tuiAutoRun) +See also: [@tuiAutoRun](ACMEScript-metatags.md#tuiAutoRun) Note: This variable is only set when the script is run from the text UI. diff --git a/docs/docs/development/ACMEScript-functions.md b/docs/docs/development/ACMEScript-functions.md index 0377feba..6b43efd9 100644 --- a/docs/docs/development/ACMEScript-functions.md +++ b/docs/docs/development/ACMEScript-functions.md @@ -17,10 +17,9 @@ Concatenate and return the stringified versions of the symbol arguments. !!! see-also "See also" [nl](#nl), [sp](#sp), [to-string](#to-string) -=== "Example" - ```lisp - (. "Time:" sp (datetime)) ;; Returns "Time: 20230308T231049.934630" - ``` +```lisp title="Example" +(. "Time:" sp (datetime)) ;; Returns "Time: 20230308T231049.934630" +``` --- @@ -37,17 +36,16 @@ The number of arguments available is stored in the variable [argc](../developmen !!! see-also "See also" [argc](../development/ACMEScript-variables.md#argc) -=== "Examples" - ```lisp - ;; Print the script name - (print "The name of the script is:" (argv 0)) +```lisp title="Example" +;; Print the script name +(print "The name of the script is:" (argv 0)) - ;; Print the first argument - (print "The first argument is:" (argv 1)) +;; Print the first argument +(print "The first argument is:" (argv 1)) - ;; Print script name and all arguments - (print "All arguments:" argv) - ``` +;; Print script name and all arguments +(print "All arguments:" argv) +``` --- @@ -57,11 +55,10 @@ The number of arguments available is stored in the variable [argc](../developmen The `assert` function terminates the script if its argument evaluates to *false*. -=== "Example" - ```lisp - (assert (== (get-configuration "cse.type") - "IN")) ;; Terminates when the setting is different from "IN" - ``` +```lisp title="Example" +(assert (== (get-configuration "cse.type") + "IN")) ;; Terminates when the setting is different from "IN" +``` --- @@ -74,10 +71,9 @@ This function encodes a string as base64. !!! see-also "See also" [url-encode](#url-encode) -=== "Example" - ```lisp - (base64-encode "Hello, World") ;; Returns "SGVsbG8sIFdvcmxk" - ``` +```lisp title="Example" +(base64-encode "Hello, World") ;; Returns "SGVsbG8sIFdvcmxk" +``` --- @@ -93,32 +89,30 @@ The result of the last expression is returned. A block can be exited early with !!! see-also "See also" [return-from](#return-from) -=== "Examples" - ```lisp - (block "myBlock" 1 2 3) ;; Returns 3 - (block "myBlock" 1 (return-from "myBlock" 2) 3) ;; Returns 2 - ``` +```lisp title="Examples" +(block "myBlock" 1 2 3) ;; Returns 3 +(block "myBlock" 1 (return-from "myBlock" 2) 3) ;; Returns 2 +``` One can use the `block` function to implement *break* and *continue* functionality in loops: -=== "Examples" - ```lisp - ;; Example for a break block - ;; The following example breaks the loop when the value of "i" is 5 - (block break - (dotimes (i 10) - ((print i) - (if (== i 5) - (return-from break))))) - - ;; Example for a continue block - ;; The following example skips the value of "i" when it is 5 +```lisp title="Examples" +;; Example for a break block +;; The following example breaks the loop when the value of "i" is 5 +(block break (dotimes (i 10) - (block continue - (if (== i 5) - (return-from continue)) - (print i))) - ``` + ((print i) + (if (== i 5) + (return-from break))))) + +;; Example for a continue block +;; The following example skips the value of "i" when it is 5 +(dotimes (i 10) + (block continue + (if (== i 5) + (return-from continue)) + (print i))) +``` --- @@ -134,10 +128,9 @@ The `car` function returns the first symbol from a list. It doesn't change the o !!! see-also "See also" [cdr](#cdr), [nth](#nth) -=== "Example" - ```lisp - (car '(1 2 3)) ;; Returns 1 - ``` +```lisp title="Example" +(car '(1 2 3)) ;; Returns 1 +``` --- @@ -156,13 +149,12 @@ Each of these list contains two symbols that are handled in order: The special symbol `otherwise` for a *condition* s-expression always matches and can be used as a default or fallback case . -=== "Example" - ```lisp - (case aSymbol - ( 1 (print "Result: 1")) - ( (+ 1 1) (print "Result: 2")) - (otherwise (print "Result: something else"))) - ``` +```lisp title="Example" +(case aSymbol + ( 1 (print "Result: 1")) + ( (+ 1 1) (print "Result: 2")) + (otherwise (print "Result: something else"))) +``` --- @@ -178,10 +170,9 @@ The `cdr` function returns a list with all symbols from a list except the first !!! see-also "See also" [car](#car), [nth](#nth) -=== "Example" - ```lisp - (cdr '(1 2 3)) ;; Returns (2 3) - ``` +```lisp title="Example" +(cdr '(1 2 3)) ;; Returns (2 3) +``` --- @@ -194,12 +185,11 @@ The `cons` function adds a new symbol to the front of a list and returns it. It !!! Note A list needs to be quoted when used directly. -=== "Example" - ```lisp - (cons 1 2) ;; Returns (1 2) - (cons 1 '(2 3)) ;; Returns (1 2 3) - (cons '(1 2) '(3 4)) ;; Returns ((1 2) 3 4) - ``` +```lisp title="Examples" +(cons 1 2) ;; Returns (1 2) +(cons 1 '(2 3)) ;; Returns (1 2 3) +(cons '(1 2) '(3 4)) ;; Returns ((1 2) 3 4) +``` --- @@ -211,11 +201,10 @@ The `datetime` function returns the current date and time. As a default, if not All timestamps are UTC-based. -=== "Examples" - ```lisp - (datetime) ;; Returns a timestamp, e.g. 20230302T221625.771604 - (datetime "%H:%M") ;; Returns, for example, "22:16" - ``` +```lisp title="Examples" +(datetime) ;; Returns a timestamp, e.g. 20230302T221625.771604 +(datetime "%H:%M") ;; Returns, for example, "22:16" +``` --- @@ -236,21 +225,20 @@ The result of a function is the result of the expression that is evaluated last !!! see-also "See also" [lambda](#lambda), [return](#return) -=== "Example" - ```lisp - (defun greeting (name) ;; define the function - (print "hello" name)) - (greeting "Arthur") ;; call the function +```lisp title="Example" +(defun greeting (name) ;; define the function + (print "hello" name)) +(greeting "Arthur") ;; call the function - ;; Fibonacci - (defun fib (n) ;; Define the function - (if (< n 2) - n - (+ (fib (- n 1)) - (fib (- n 2))) - )) - (fib 10) ;; Returns 55 - ``` +;; Fibonacci +(defun fib (n) ;; Define the function + (if (< n 2) + n + (+ (fib (- n 1)) + (fib (- n 2))) + )) +(fib 10) ;; Returns 55 +``` --- @@ -265,12 +253,11 @@ The function returns the variable's new value. !!! see-also "See also" [inc](#inc) -=== "Example" - ```lisp - (setq a 1) ;; Set variable "a" to 1 - (dec a) ;; Decrement variable "a" by 1 - (dec a 2.5) ;; Decrement variable "a" by 2.5 - ``` +```lisp title="Examples" +(setq a 1) ;; Set variable "a" to 1 +(dec a) ;; Decrement variable "a" by 1 +(dec a 2.5) ;; Decrement variable "a" by 2.5 +``` --- @@ -287,16 +274,15 @@ If the `result variable` is specified then the loop returns the value of that va !!! see-also "See also" [dotimes](#dotimes), [while](#while) -=== "Example" - ```lisp - (dolist (i '(1 2 3 4 5 6 7 8 9 10)) - (print i) ;; print 1..10 +```lisp title="Examples" +(dolist (i '(1 2 3 4 5 6 7 8 9 10)) + (print i) ;; print 1..10 - (setq result 0) - (dolist (i '(1 2 3 4 5 6 7 8 9 10) result) - (setq result (+ result i))) ;; sum 1..10 - (print result) ;; 55 - ``` +(setq result 0) +(dolist (i '(1 2 3 4 5 6 7 8 9 10) result) + (setq result (+ result i))) ;; sum 1..10 +(print result) ;; 55 +``` --- @@ -313,16 +299,15 @@ If the `result variable` is specified then the loop returns the value of that va !!! see-also "See also" [dolist](#dolist), [while](#while) -=== "Example" - ```lisp - (dotimes (i 10) - (print i) ;; print 0..9 +```lisp title="Examples" +(dotimes (i 10) + (print i) ;; print 0..9 - (setq result 0) - (dotimes (i 10 result) - (setq result (+ result i))) ;; sum 0..9 - (print result) ;; 45 - ``` +(setq result 0) +(dotimes (i 10 result) + (setq result (+ result i))) ;; sum 0..9 +(print result) ;; 45 +``` --- @@ -335,10 +320,9 @@ The `eval` function evaluates and executes a quoted list or symbol. !!! see-also "See also" [parse-string](#parse-string), [progn](#progn), [to-symbol](#to-symbol) -=== "Example" - ```lisp - (eval '(print "Hello, World")) ;; Prints "Hello, World" - ``` +```lisp title="Example" +(eval '(print "Hello, World")) ;; Prints "Hello, World" +``` --- @@ -348,11 +332,10 @@ The `eval` function evaluates and executes a quoted list or symbol. With this function one can disable or enable the [evaluation of s-expressions in strings](../development/ACMEScript.md#evaluating-s-expressions-in-strings-and-json-structures). -=== "Example" - ```lisp - (evaluate-inline false) ;; Disables inline evaluation - (print "1 + 2 = [(+ 1 2)]") ;; Prints "1 + 2 = [(+ 1 2)]" - ``` +```lisp title="Example" +(evaluate-inline false) ;; Disables inline evaluation +(print "1 + 2 = [(+ 1 2)]") ;; Prints "1 + 2 = [(+ 1 2)]" +``` --- @@ -369,13 +352,12 @@ The `get-json-attribute` function retrieves an attribute from a JSON structure v !!! see-also "See also" [has-json-attribute](#has-json-attribute), [remove-json-attribute](#remove-json-attribute), [set-json-attribute](#set-json-attribute) -=== "Examples" - ```lisp - (get-json-attribute { "a" : { "b" : "c" }} "a/b" ) ;; Returns "c" - (get-json-attribute { "a" : [ "b", "c" ]} "a/{0}" ) ;; Returns "b" - (get-json-attribute { "a" : [ "b", "c" ]} "a/{}" ) ;; Returns ( "b" "c" ) - (get-json-attribute { "a" : [ "b", "c" ]} "{*}/{0}" ) ;; Returns "b" - ``` +```lisp title="Examples" +(get-json-attribute { "a" : { "b" : "c" }} "a/b" ) ;; Returns "c" +(get-json-attribute { "a" : [ "b", "c" ]} "a/{0}" ) ;; Returns "b" +(get-json-attribute { "a" : [ "b", "c" ]} "a/{}" ) ;; Returns ( "b" "c" ) +(get-json-attribute { "a" : [ "b", "c" ]} "{*}/{0}" ) ;; Returns "b" +``` --- @@ -392,12 +374,10 @@ This *key* may be a structured path to access elements deeper down in the JSON s See [get-json-attribute](#get-json-attribute) for further details on how to access JSON attributes. - -=== "Examples" - ```lisp - (has-json-attribute { "a" : { "b" : "c" }} "a/b" ) ;; Returns true - (has-json-attribute { "a" : { "b" : "c" }} "a/c" ) ;; Returns false - ``` +```lisp title="Examples" +(has-json-attribute { "a" : { "b" : "c" }} "a/b" ) ;; Returns true +(has-json-attribute { "a" : { "b" : "c" }} "a/c" ) ;; Returns false +``` --- @@ -411,12 +391,11 @@ If it evaluates to *false* then the third (optional) argument is executed, if pr The boolean expression can be any s-expression that evaluates to a boolean value or *nil*, or a list or a string. *nil* values, empty lists, or zero-length strings evaluate to *false*, or to *true* otherwise. -=== "Example" - ```lisp - (if (< 1 2) ;; Evaluates to "true" - (print "true") ;; This expression is executed - (print "false")) ;; This expression is not executed - ``` +```lisp title="Examples" +(if (< 1 2) ;; Evaluates to "true" + (print "true") ;; This expression is executed + (print "false")) ;; This expression is not executed +``` --- @@ -430,13 +409,12 @@ The function returns *true* if this is the case, or *false* otherwise. !!! see-also "See also" [index-of](#index-of) -=== "Example" - ```lisp - (in "Hello" "Hello, World") ;; Returns true - (in "Bye" "Hello, World") ;; Returns false - (in 1 '(1 2 3)) ;; Returns true - (in '(1 2) '((1 2) (3 4))) ;; Returns true - ``` +```lisp title="Examples" +(in "Hello" "Hello, World") ;; Returns true +(in "Bye" "Hello, World") ;; Returns false +(in 1 '(1 2 3)) ;; Returns true +(in '(1 2) '((1 2) (3 4))) ;; Returns true +``` --- @@ -453,12 +431,11 @@ The function returns the variable's new value. !!! see-also "See also" [dec](#dec) -=== "Example" - ```lisp - (setq a 1) ;; Set variable "a" to 1 - (inc a) ;; Increment variable "a" by 1 - (inc a 2.5) ;; Increment variable "a" by 2.5 - ``` +```lisp title="Example" +(setq a 1) ;; Set variable "a" to 1 +(inc a) ;; Increment variable "a" by 1 +(inc a 2.5) ;; Increment variable "a" by 2.5 +``` --- @@ -474,12 +451,11 @@ The function returns the index as a number, or *nil* if the value could not be f !!! see-also "See also" [in](#in), [nth](#nth) -=== "Example" - ```lisp - (index-of 1 '(1 2 3)) ;; Returns 0 - (index-of "a" '("b", "c", "d")) ;; Returns nil - (index-of "b" "abc") ;; Returns 1 - ``` +```lisp title="Examples" +(index-of 1 '(1 2 3)) ;; Returns 0 +(index-of "a" '("b", "c", "d")) ;; Returns nil +(index-of "b" "abc") ;; Returns 1 +``` --- @@ -489,12 +465,11 @@ The function returns the index as a number, or *nil* if the value could not be f The `is-defined` function tests whether a symbol (ie. a variable, built-in or defined function) is defined. -=== "Example" - ```lisp - (setq a 1) ;; Define variable "a" - (is-defined 'a) ;; Evaluates to "true". - (is-defined 'b) ;; Evaluates to "false" - ``` +```lisp title="Examples" +(setq a 1) ;; Define variable "a" +(is-defined 'a) ;; Evaluates to "true". +(is-defined 'b) ;; Evaluates to "false" +``` !!! note Most of the time the symbol argument needs to be quoted, otherwise the symbol is evaluated first and the function will not work as expected. @@ -510,10 +485,9 @@ The `json-to-string` function returns a JSON structure in a string. !!! see-also "See also" [string-to-json](#string-to-json), [to-number](#to-number), [to-string](#to-string) -=== "Example" - ```lisp - (json-to-string { "a" : { "b" : "c" }}) ;; Returns "{\"a\": {\"b\": \"c\"}}" - ``` +```lisp title="Example" +(json-to-string { "a" : { "b" : "c" }}) ;; Returns "{\"a\": {\"b\": \"c\"}}" +``` --- @@ -526,10 +500,9 @@ The `jsonify` function returns a string where characters are escaped that would !!! see-also "See also" [string-to-json](#string-to-json), [to-number](#to-number), [to-string](#to-string) -=== "Example" - ```lisp - (jsonify "Hello, World") ;; Returns "Hello,\nWorld" - ``` +```lisp title="Example" +(jsonify "Hello, World") ;; Returns "Hello,\nWorld" +``` --- @@ -550,14 +523,13 @@ The result of a lambda function is the result of the expression that is evaluate !!! see-also "See also" [defun](#defun), [return](#return) -=== "Example" - ```lisp - ((lambda (x) (* x x)) 5) ;; Returns 25 +```lisp title="Examples" +((lambda (x) (* x x)) 5) ;; Returns 25 - (setq y (lambda (x) (* x x))) ;; Define and assign lambda function - (y) ;; Returns ( ( x ) ( * x x ) ) - ((y) 5) ;; Returns 25 - ``` +(setq y (lambda (x) (* x x))) ;; Define and assign lambda function +(y) ;; Returns ( ( x ) ( * x x ) ) +((y) 5) ;; Returns 25 +``` --- @@ -567,11 +539,10 @@ The result of a lambda function is the result of the expression that is evaluate The `length` function returns the length of a string or the number of elements in a list. -=== "Example" - ```lisp - (length "Hello, World") ;; Returns 12 - (length '(1 2 3)) ;; Returns 3 - ``` +```lisp title="Examples" +(length "Hello, World") ;; Returns 12 +(length '(1 2 3)) ;; Returns 3 +``` ### let* @@ -588,12 +559,11 @@ Each assignment consists, like the [setq](#setq) function, of an implicit quoted !!! see-also "See also" [setq](#setq) -=== "Example" - ```lisp - (let* (a 1) ;; Assigns 1 to a - (a (+ a 1))) ;; Assigns a + 1 = 2 to a - (let* (b 2) (c 3)) ;; Assigns 2 to b and 3 to c - ``` +```lisp title="Examples" +(let* (a 1) ;; Assigns 1 to a + (a (+ a 1))) ;; Assigns a + 1 = 2 to a +(let* (b 2) (c 3)) ;; Assigns 2 to b and 3 to c +``` --- @@ -606,10 +576,9 @@ The `list` function returns a list that contains all the symbol arguments. !!! see-also "See also" [cons](#cons) -=== "Example" - ```lisp - (list 1 2 3) ;; Returns ( 1 2 3 ) - ``` +```lisp title="Example" +(list 1 2 3) ;; Returns ( 1 2 3 ) +``` --- @@ -624,10 +593,9 @@ The function always returns *nil*. !!! see-also "See also" [log-error](#log-error), [print](#print) -=== "Example" - ```lisp - (log "Hello, World") ;; Prints "Hello, World" to the log - ``` +```lisp title="Example" +(log "Hello, World") ;; Prints "Hello, World" to the log +``` --- @@ -642,10 +610,9 @@ The function always returns *nil*. !!! see-also "See also" [log](#log), [print](#print) -=== "Example" - ```lisp - (log-error "Hello, World") ;; Prints "Hello, World" to the warning log - ``` +```lisp title="Example" +(log-error "Hello, World") ;; Prints "Hello, World" to the warning log +``` --- @@ -658,10 +625,9 @@ The `lower` function returns a lower case copy of the input string. !!! see-also "See also" [upper](#upper) -=== "Example" - ```lisp - (lower "Hello, World") ;; Returns "hello, world" - ``` +```lisp title="Example" +(lower "Hello, World") ;; Returns "hello, world" +``` --- @@ -679,11 +645,10 @@ The `match` function determines whether a string matches a regular expression *r - `+` : one or more characters - `\` : Escape an expression operator -=== "Example" - ```lisp - (match "aa" "a?") ;; Returns true - (match "aa" "b*") ;; Returns false - ``` +```lisp title="Examples" +(match "aa" "a?") ;; Returns true +(match "aa" "b*") ;; Returns false +``` --- @@ -696,10 +661,9 @@ The `nl` function returns a newline character. !!! see-also "See also" [print](#print), [sp](#sp) -=== "Example" - ```lisp - (. "Hello," nl "World") ;; Returns "Hello,\nWorld" - ``` +```lisp title="Example" +(. "Hello," nl "World") ;; Returns "Hello,\nWorld" +``` --- @@ -714,11 +678,10 @@ The index is 0-based. !!! see-also "See also" [car](#car), [cdr](#cdr), [index-of](#index-of) -=== "Example" - ```lisp - (nth 2 '(1 2 3)) ;; Returns 3 - (nth 2 "Hello, World") ;; Returns "l" - ``` +```lisp title="Examples" +(nth 2 '(1 2 3)) ;; Returns 3 +(nth 2 "Hello, World") ;; Returns "l" +``` --- @@ -731,10 +694,9 @@ The `parse-string` function parses a string that contains an s-expression and re !!! see-also "See also" [eval](#eval), [to-symbol](#to-symbol) -=== "Example" - ```lisp - (eval (parse-string "(print \"hello, world\")")) ;; Prints "hello, world" - ``` +```lisp title="Example" +(eval (parse-string "(print \"hello, world\")")) ;; Prints "hello, world" +``` --- @@ -749,10 +711,9 @@ The function always returns *nil*. !!! see-also "See also" [log](#log), [log-error](#log-error) -=== "Example" - ```lisp - (print "Hello, World") ;; Prints "Hello, World" - ``` +```lisp title="Example" +(print "Hello, World") ;; Prints "Hello, World" +``` --- @@ -768,10 +729,9 @@ This function is implicitly used internally when used to evaluate s-expressions. !!! see-also "See also" [eval](#eval) -=== "Example" - ```lisp - (progn (print "Hello, World") 1) ;; Prints "Hello, World" and returns 1 - ``` +```lisp title="Example" +(progn (print "Hello, World") 1) ;; Prints "Hello, World" and returns 1 +``` --- @@ -786,11 +746,10 @@ If a symbol is provided for the optional argument its value is taken as the resu !!! see-also "See also" [quit-with-error](#quit-with-error) -=== "Example" - ```lisp - (quit) ;; Returns nil - (quit "a result") ;; Returns "a result" - ``` +```lisp title="Examples" +(quit) ;; Returns nil +(quit "a result") ;; Returns "a result" +``` --- @@ -805,11 +764,10 @@ If a symbol is provided for the optional argument its value is taken as the resu !!! see-also "See also" [quit](#quit) -=== "Example" - ```lisp - (quit-with-error) ;; Returns nil - (quit-with-error "a result") ;; Returns "a result" - ``` +```lisp title="Examples" +(quit-with-error) ;; Returns nil +(quit-with-error "a result") ;; Returns "a result" +``` --- @@ -819,10 +777,9 @@ If a symbol is provided for the optional argument its value is taken as the resu The `quote` function returns a quoted version of the argument. It can be used to get a quoted version of an s-expression or symbol that is the result of another function. -=== "Example" - ```lisp - (quote (1 2 3)) ;; Returns (1 2 3) - ``` +```lisp title="Example" +(quote (1 2 3)) ;; Returns (1 2 3) +``` --- @@ -834,12 +791,11 @@ The `random` function generates a random float number in the given range. The default for the range, when no argument is given, is [0.0, 1.0]. If one number argument is given then this indicates a range of [0.0, \]. If two number arguments are given then this indicates a range of [\, \]. -=== "Example" - ```lisp - (random) ;; Returns, for example, 0.748786 - (random 10) ;; Returns, for example, 4.976338 - (random 10 20) ;; returns, for example, 12.73221 - ``` +```lisp title="Examples" +(random) ;; Returns, for example, 0.748786 +(random 10) ;; Returns, for example, 4.976338 +(random 10 20) ;; returns, for example, 12.73221 +``` --- @@ -854,10 +810,9 @@ The function doesn't change the original JSON structure, but returns an updated !!! see-also "See also" [get-json-attribute](#get-json-attribute), [has-json-attribute](#has-json-attribute), [set-json-attribute](#set-json-attribute) -=== "Examples" - ```lisp - (remove-json-attribute { "a" : { "b" : "c" }} "a/b") ;; Returns { "a" : {} } - ``` +```lisp title="Examples" +(remove-json-attribute { "a" : { "b" : "c" }} "a/b") ;; Returns { "a" : {} } +``` --- @@ -870,12 +825,11 @@ The `return` function stops the evaluation of a function or [while](#while) loop !!! see-also "See also" [defun](#defun), [while](#while) -=== "Example" - ```lisp - (if (< 1 2) ;; Evaluates to "true" - (return 23) ;; Return the number 23 - ) - ``` +```lisp title="Example" +(if (< 1 2) ;; Evaluates to "true" + (return 23) ;; Return the number 23 +) +``` --- @@ -890,10 +844,9 @@ The function may return a symbol, or *nil*. !!! see-also "See also" [block](#block) -=== "Example" - ```lisp - (block "myBlock" 1 (return-from "myBlock" 2) 3) ;; Returns 2 - ``` +```lisp title="Examples" +(block "myBlock" 1 (return-from "myBlock" 2) 3) ;; Returns 2 +``` --- @@ -903,11 +856,10 @@ The function may return a symbol, or *nil*. The `round` function rounds a number to *precision* digits after the decimal point. The default is 0, meaning to round to nearest integer. -=== "Example" - ```lisp - (round 3.1415926) ;; Returns 3 - (round 3.1415926 2) ;; Returns 3.14 - ``` +```lisp title="Examples" +(round 3.1415926) ;; Returns 3 +(round 3.1415926 2) ;; Returns 3.14 +``` --- @@ -916,7 +868,6 @@ The `round` function rounds a number to *precision* digits after the decimal poi `(set-json-attribute )` `(set-json-attribute '( '( )* )` - The `set-json-attribute` function adds or updates an attribute in a JSON structure via a *key* path to the new *value*. This *key* may be a structured path to access elements deeper down in the JSON structure. See [get-json-attribute](#get-json-attribute) for further details on how to access JSON attributes. There are two forms to use this function: @@ -932,11 +883,10 @@ The function doesn't change the original JSON structure, but returns an updated !!! see-also "See also" [get-json-attribute](#get-json-attribute), [has-json-attribute](#has-json-attribute), [remove-json-attribute](#remove-json-attribute) -=== "Examples" - ```lisp - (set-json-attribute { "a" : { "b" : "c" }} "a/b" "e") ;; Returns {"a": {"b": "e"}} - (set-json-attribute { "a" : { "b" : "c" }} '('("a/b" "d") '("a/c" "e"))) ;; Returns { "a" : { "b" : "d", "c" : "e"} - ``` +```lisp title="Examples" +(set-json-attribute { "a" : { "b" : "c" }} "a/b" "e") ;; Returns {"a": {"b": "e"}} +(set-json-attribute { "a" : { "b" : "c" }} '('("a/b" "d") '("a/c" "e"))) ;; Returns { "a" : { "b" : "d", "c" : "e"} +``` --- @@ -949,10 +899,9 @@ The `setq` function assigns a value to a variable. !!! see-also "See also" [let*](#let-star) -=== "Example" - ```lisp - (setq a "Hello, World") ;; Returns "Hello, World" and sets the variable "a" - ``` +```lisp title="Example" +(setq a "Hello, World") ;; Returns "Hello, World" and sets the variable "a" +``` --- @@ -966,10 +915,9 @@ If the script execution timeouts during a sleep, the function is interrupted and The function returns the delay. -=== "Example" - ```lisp - (sleep 1.5) ;; Sleep for 1.5 seconds - ``` +```lisp title="Example" +(sleep 1.5) ;; Sleep for 1.5 seconds +``` --- @@ -981,13 +929,12 @@ The `slice` function returns the slice of a list or a string. The behavior is the same as slicing in Python, except that both *start* and *end* must be provided. The first argument is the *start* (including) of the slice, the second is the *end* (excluding) of the slice. The fourth argument is the list or string to slice. -=== "Example" - ```lisp - (slice 1 2 '(1 2 3)) ;; Returns (2) - (slice 0 -1 "abcde") ;; Returns "abcd" - (slice -1 99 "abcde") ;; Returns "e" - (slice 99 100 '(1 2 3)) ;; Returns () - ``` +```lisp title="Examples" +(slice 1 2 '(1 2 3)) ;; Returns (2) +(slice 0 -1 "abcde") ;; Returns "abcd" +(slice -1 99 "abcde") ;; Returns "e" +(slice 99 100 '(1 2 3)) ;; Returns () +``` --- @@ -1000,10 +947,9 @@ The `sp` function returns a space character. !!! see-also "See also" [print](#print), [nl](#nl) -=== "Example" - ```lisp - (. "Hello," sp "World") ;; Returns "Hello, World" - ``` +```lisp title="Example" +(. "Hello," sp "World") ;; Returns "Hello, World" +``` --- @@ -1016,10 +962,9 @@ The `string-to-json` function converts a string to a JSON structure and returns !!! see-also "See also" [json-to-string](#json-to-string), [to-number](#to-number), [jsonify](#jsonify) -=== "Example" - ```lisp - (string-to-json "{ \"a\" : { \"b\" : \"c\" }}") ;; Returns {"a": {"b": "c"}} - ``` +```lisp title="Example" +(string-to-json "{ \"a\" : { \"b\" : \"c\" }}") ;; Returns {"a": {"b": "c"}} +``` --- @@ -1032,10 +977,9 @@ The `to-number` function converts a string that contains a number to a number sy !!! see-also "See also" [json-to-string](#json-to-string), [to-string](#to-string) -=== "Example" - ```lisp - (to-number "123") ;; Returns the number 123 - ``` +```lisp title="Example" +(to-number "123") ;; Returns the number 123 +``` --- @@ -1048,10 +992,9 @@ The `to-string` function converts a symbol of any of the built-in types to a str !!! see-also "See also" [json-to-string](#json-to-string), [to-number](#to-number) -=== "Example" - ```lisp - (to-string '(1 2)) ;; Returns "[1, 2]" - ``` +```lisp title="Example" +(to-string '(1 2)) ;; Returns "[1, 2]" +``` --- @@ -1064,10 +1007,9 @@ The `to-symbol` function converts a string to a symbol and returns it. The resul !!! see-also "See also" [eval](#eval), [parse-string](#parse-string), [parse-string](#parse-string) -=== "Example" - ```lisp - (to-symbol "a-symbol") ;; Returns the symbol 'a-symbol' - ``` +```lisp title="Example" +(to-symbol "a-symbol") ;; Returns the symbol 'a-symbol' +``` --- @@ -1084,13 +1026,12 @@ Currently only programmatic flow interrupts are supported to trigger the cleanup The function always returns the result of the cleanup s-expression. -=== "Example" - ```lisp - ;; Prints "main form" and "cleanup form" and returns 2 - (unwind-protect - ((print "main form") 1) - ((print "cleanup form") 2)) - ``` +```lisp title="Example" +;; Prints "main form" and "cleanup form" and returns 2 +(unwind-protect + ((print "main form") 1) + ((print "cleanup form") 2)) +``` --- @@ -1103,10 +1044,9 @@ The `upper` function returns an upper case copy of the input string. !!! see-also "See also" [lower](#lower) -=== "Example" - ```lisp - (upper "Hello, World") ;; Returns "HELLO, WORLD" - ``` +```lisp title="Example" +(upper "Hello, World") ;; Returns "HELLO, WORLD" +``` --- @@ -1119,10 +1059,9 @@ The `url-encode` function encodes a string so that may be safely used as part of !!! see-also "See also" [base64-encode](#base64-encode) -=== "Example" - ```lisp - (url-encode "Hello, World") ;; Returns "Hello%2C+World" - ``` +```lisp title="Example" +(url-encode "Hello, World") ;; Returns "Hello%2C+World" +``` --- @@ -1141,13 +1080,12 @@ The `while` function returns the result of the last evaluated s-expression in th !!! see-also "See also" [dolist](#dolist), [dotime](#dotimes), [return](#return) -=== "Example" - ```lisp - (setq i 0) ;; Set loop variable - (while (< i 10) ;; Loop 10 times - ((print i) ;; Print to the console - (inc i))) ;; Increment loop variable - ``` +```lisp title="Example" +(setq i 0) ;; Set loop variable +(while (< i 10) ;; Loop 10 times + ((print i) ;; Print to the console + (inc i))) ;; Increment loop variable +``` ## CSE-Specific Functions @@ -1160,10 +1098,9 @@ The following functions provide support to access certain CSE functionalities, c The `clear-console` function clears the console screen. -=== "Example" - ```lisp - (clear-console) ;; Clears the console screen - ``` +```lisp title="Example" +(clear-console) ;; Clears the console screen +``` --- @@ -1180,10 +1117,9 @@ The function returns a quoted list where each entry is another quoted list with - attribute long name - attribute type -=== "Example" - ```lisp - (cse-attribute-info "acop") ;; Returns ( ( "acop" "accessControlOperations" "nonNegInteger" ) ) - ``` +```lisp title="Example" +(cse-attribute-info "acop") ;; Returns ( ( "acop" "accessControlOperations" "nonNegInteger" ) ) +``` --- @@ -1201,10 +1137,9 @@ The return value is one of the following strings: - STOPPED - RESETTING -=== "Example" - ```lisp - (cse-status) ;; Returns "RUNNING" - ``` +```lisp title="Example" +(cse-status) ;; Returns "RUNNING" +``` --- @@ -1217,11 +1152,10 @@ The `get-config` function retrieves a setting from the CSE's internal configurat !!! see-also "See also" [has-config](#has-config), [set-config](#set-config) -=== "Examples" - ```lisp - (get-config "cse.type") ;; Returns, for example, 1 - (get-config "cse.cseID") ;; Returns, for example, "/id-in" - ``` +```lisp title="Examples" +(get-config "cse.type") ;; Returns, for example, 1 +(get-config "cse.cseID") ;; Returns, for example, "/id-in" +``` --- @@ -1237,10 +1171,9 @@ The `get-loglevel` function retrieves a the CSE's current log level setting. The - ERROR - OFF -=== "Example" - ```lisp - (get-loglevel) ;; Return, for example, INFO - ``` +```lisp title="Example" +(get-loglevel) ;; Return, for example, INFO +``` --- @@ -1253,10 +1186,9 @@ The `get-storage` function retrieves a value from the CSE's internal script-data !!! see-also "See also" [has-storage](#has-storage), [put-storage](#put-storage), [remove-storage](#remove-storage) -=== "Examples" - ```lisp - (get-storage "aStorageID" "aKey") ;; Retrieves the value for "aKey" from "aStorageID" - ``` +```lisp title="Example" +(get-storage "aStorageID" "aKey") ;; Retrieves the value for "aKey" from "aStorageID" +``` --- @@ -1269,11 +1201,10 @@ The `has-config` function determines whether a setting from the CSE's internal c !!! see-also "See also" [get-config](#get-config), [set-config](#set-config) -=== "Examples" - ```lisp - (has-config "cse.cseID") ;; Returns true - (has-config "cse.unknown") ;; Returns false - ``` +```lisp title="Examples" +(has-config "cse.cseID") ;; Returns true +(has-config "cse.unknown") ;; Returns false +``` --- @@ -1286,10 +1217,9 @@ The `has-storage` function determines whether a value has been stored under the !!! see-also "See also" [get-storage](#get-storage), [put-storage](#put-storage), [remove-storage](#remove-storage) -=== "Examples" - ```lisp - (has-storage "aStorageID" "aKey") ;; Tests whether the key "aKey" exists in "aStorageID" - ``` +```lisp title="Example" +(has-storage "aStorageID" "aKey") ;; Tests whether the key "aKey" exists in "aStorageID" +``` --- @@ -1304,10 +1234,9 @@ The function returns the result of the finished script. !!! see-also "See also" [run-script](#run-script), [schedule-next-script](#schedule-next-script) -=== "Example" - ```lisp - (include-script "functions" "an argument") ;; Run the script "functions" - ``` +```lisp title="Example" +(include-script "functions" "an argument") ;; Run the script "functions" +``` --- @@ -1317,11 +1246,10 @@ The function returns the result of the finished script. The `log-divider` function inserts a divider line in the CSE's *DEBUG* log. It can help to easily identify the different sections when working with many requests. An optional (short) message can be provided in the argument. -=== "Examples" - ```lisp - (log-divider) ;; Add a divider - (log-divider "Hello, World") ;; Add a divider with a centered message - ``` +```lisp title="Examples" +(log-divider) ;; Add a divider +(log-divider "Hello, World") ;; Add a divider with a centered message +``` --- @@ -1331,10 +1259,9 @@ The `log-divider` function inserts a divider line in the CSE's *DEBUG* log. It c The `print-json` function prints a JSON structure with syntax highlighting to the console. -=== "Example" - ```lisp - (print-json { "m2m:cnt" : { "rn": "myCnt" }}) ;; Print the JSON structure - ``` +```lisp title="Example" +(print-json { "m2m:cnt" : { "rn": "myCnt" }}) ;; Print the JSON structure +``` --- @@ -1347,10 +1274,9 @@ The `put-storage` function inserts or updates a *value* in the CSE's internal sc !!! see-also "See also" [get-storage](#get-storage), [has-storage](#has-storage), [remove-storage](#remove-storage) -=== "Examples" - ```lisp - (put-storage "aStorageID" "aKey" "Hello, World") ;; Inserts or updates the key "aKey" in "aStorageID" - ``` +```lisp title="Example" +(put-storage "aStorageID" "aKey" "Hello, World") ;; Inserts or updates the key "aKey" in "aStorageID" +``` --- @@ -1368,11 +1294,10 @@ With two parameters the `remove-storage` function removes a *key*/*value* pair f !!! see-also "See also" [get-storage](#get-storage), [has-storage](#has-storage), [put-storage](#put-storage) -=== "Examples" - ```lisp - (remove-storage "aStorageID" "aKey") ;; Removes the key and value from storageID - (remove-storage "aStorageID") ;; Removes all keys and value from storageID - ``` +```lisp title="Examples" +(remove-storage "aStorageID" "aKey") ;; Removes the key and value from storageID +(remove-storage "aStorageID") ;; Removes all keys and value from storageID +``` --- @@ -1384,10 +1309,9 @@ The `reset-cse` function initiates a CSE reset. The script execution does continue after the CSE finished the reset. -=== "Example" - ```lisp - (reset-cse) ;; Resets the CSE - ``` +```lisp title="Example" +(reset-cse) ;; Resets the CSE +``` --- @@ -1402,10 +1326,9 @@ The function returns the result of the finished script. !!! see-also "See also" [include-script](#include-script), [schedule-next-script](#schedule-next-script) -=== "Example" - ```lisp - (setq result (run-script "aScript" "an argument")) ;; Run the script "aScript" and assign the result - ``` +```lisp title="Example" +(setq result (run-script "aScript" "an argument")) ;; Run the script "aScript" and assign the result +``` --- @@ -1415,10 +1338,9 @@ The function returns the result of the finished script. The `runs-in-ipython` function determines whether the CSE currently runs in an IPython environment, such as Jupyter Notebooks. -=== "Example" - ```lisp - (runs-in-ipython) ;; Returns true if the CSE runs in an iPython environment - ``` +```lisp title="Example" +(runs-in-ipython) ;; Returns true if the CSE runs in an iPython environment +``` --- @@ -1433,10 +1355,9 @@ This is different from [include-script](#include-script) and [run-script](#run-s !!! see-also "See also" [include-script](#include-script), [run-script](#run-script) -=== "Example" - ```lisp - (schedule-next-script "scriptName" "anArgument") ;; Schedule a script with an argument - ``` +```lisp title="Example" +(schedule-next-script "scriptName" "anArgument") ;; Schedule a script with an argument +``` --- @@ -1451,10 +1372,9 @@ It is only possible to update an existing setting, but not to create a new one. !!! see-also "See also" [get-config](#get-config), [has-config](#has-config) -=== "Examples" - ```lisp - (set-config "cse.checkExpirationsInterval" 1.5) ;; Set the configuration to 1.5 - ``` +```lisp title="Example" +(set-config "cse.checkExpirationsInterval" 1.5) ;; Set the configuration to 1.5 +``` --- @@ -1464,10 +1384,9 @@ It is only possible to update an existing setting, but not to create a new one. The `set-console-logging` function enables or disables console logging. It does not turn on or off logging in general. [Printing](#print) to the console is not affected. -=== "Example" - ```lisp - (set-console-logging false) ;; Switch off console logging - ``` +```lisp title="Example" +(set-console-logging false) ;; Switch off console logging +``` ## oneM2M-Specific Functions @@ -1499,13 +1418,12 @@ The function returns a list: !!! see-also "See also" [delete-resource](#delete-resource), [import-raw](#import-raw), [retrieve-resource](#retrieve-resource), [send-notification](#send-notification), [update-resource](#update-resource) -=== "Examples" - ```lisp - (create-resource "CAdmin" "cse-in" { "m2m:cnt" : { "rn": "myCnt" }}) ;; Returns ( 2001 { "m2m:cnt" ... } ) - - ;; Provide extra requestVersionIndicator - (create-resource "CAdmin" "cse-in" { "m2m:cnt" : { }} { "rvi": "3"}) ;; Returns ( 2001 { "m2m:cnt" ... } ) - ``` +```lisp title="Example" +(create-resource "CAdmin" "cse-in" { "m2m:cnt" : { "rn": "myCnt" }}) ;; Returns ( 2001 { "m2m:cnt" ... } ) + +;; Provide extra requestVersionIndicator +(create-resource "CAdmin" "cse-in" { "m2m:cnt" : { }} { "rvi": "3"}) ;; Returns ( 2001 { "m2m:cnt" ... } ) +``` --- @@ -1533,13 +1451,12 @@ The function returns a list: !!! see-also "See also" [create-resource](#create-resource), [retrieve-resource](#retrieve-resource), [send-notification](#send-notification), [update-resource](#update-resource) -=== "Examples" - ```lisp - (delete-resource "CAdmin" "cse-in/myCnt") ;; Returns ( 2002 { "m2m:cnt" ... } ) - - ;; Provide extra requestVersionIndicator - (delete-resource "CAdmin" "cse-in/myCnt" { "rvi": "3"}) ;; Returns ( 2002 { "m2m:cnt" ... } ) - ``` +```lisp title="Example" +(delete-resource "CAdmin" "cse-in/myCnt") ;; Returns ( 2002 { "m2m:cnt" ... } ) + +;; Provide extra requestVersionIndicator +(delete-resource "CAdmin" "cse-in/myCnt" { "rvi": "3"}) ;; Returns ( 2002 { "m2m:cnt" ... } ) +``` --- @@ -1559,21 +1476,20 @@ The function returns a list: - *response status* is the oneM2M Response Status Code (RSC) for the request - *resource* is the response content (usually *nil* if successful) -=== "Example" - ```lisp - ;; Add an AE resource under the CSEBase - (import-raw - "CmyAE" ;; Originator - { "m2m:ae": { - "ri": "CmyAE", - "rn": "CmyAE", - "pi": "${ (get-config \"cse.ri\") }", ;; Get the CSE's resource ID from the configuration - "rr": true, - "api": "NmyAppId", - "aei": "CmyAE", - "csz": [ "application/json", "application/cbor" ] - }}) - ``` +```lisp title="Example" +;; Add an AE resource under the CSEBase +(import-raw + "CmyAE" ;; Originator + { "m2m:ae": { + "ri": "CmyAE", + "rn": "CmyAE", + "pi": "${ (get-config \"cse.ri\") }", ;; Get the CSE's resource ID from the configuration + "rr": true, + "api": "NmyAppId", + "aei": "CmyAE", + "csz": [ "application/json", "application/cbor" ] + }}) +``` --- @@ -1594,16 +1510,15 @@ The function returns a boolean indicating the query result. !!! see-also "See also" [get-json-attribute](#get-json-attribute) -=== "Examples" - ```lisp - ;; Returns true - (query-resource - '(& (> x 100) (== rn "cnt1234")) - { "m2m:cnt": { - "rn": "cnt1234", - "x": 123 - }}) - ``` +```lisp title="Example" +;; Returns true +(query-resource + '(& (> x 100) (== rn "cnt1234")) + { "m2m:cnt": { + "rn": "cnt1234", + "x": 123 + }}) +``` --- @@ -1631,13 +1546,12 @@ The function returns a list: !!! see-also "See also" [create-resource](#create-resource), [delete-resource](#delete-resource), [send-notification](#send-notification), [update-resource](#update-resource) -=== "Examples" - ```lisp - (retrieve-resource "CAdmin" "cse-in/myCnt") ;; Returns ( 2000 { "m2m:cnt" ... } ) - - ;; Provide extra requestVersionIndicator - (retrieve-resource "CAdmin" "cse-in/myCnt" { "rvi": "3"}) ;; Returns ( 2000 { "m2m:cnt" ... } ) - ``` +```lisp title="Example" +(retrieve-resource "CAdmin" "cse-in/myCnt") ;; Returns ( 2000 { "m2m:cnt" ... } ) + +;; Provide extra requestVersionIndicator +(retrieve-resource "CAdmin" "cse-in/myCnt" { "rvi": "3"}) ;; Returns ( 2000 { "m2m:cnt" ... } ) +``` --- @@ -1666,10 +1580,9 @@ The function returns a list: !!! see-also "See also" [create-resource](#create-resource), [delete-resource](#delete-resource), [retrieve-resource](#retrieve-resource), [update-resource](#update-resource) -=== "Examples" - ```lisp - (send-notification "CAdmin" "cse-in/myAE" { "m2m:sgn" : { ... }}) ;; Returns notification result - ``` +```lisp title="Example" +(send-notification "CAdmin" "cse-in/myAE" { "m2m:sgn" : { ... }}) ;; Returns notification result +``` --- @@ -1698,13 +1611,12 @@ The function returns a list: !!! see-also "See also" [create-resource](#create-resource), [delete-resource](#delete-resource), [retrieve-resource](#retrieve-resource), [send-notification](#send-notification) -=== "Examples" - ```lisp - (update-resource "CAdmin" "cse-in" { "m2m:cnt" : { "mni": 10 }}) ;; Returns ( 2004 { "m2m:cnt" ... } ) +```lisp title="Example" +(update-resource "CAdmin" "cse-in" { "m2m:cnt" : { "mni": 10 }}) ;; Returns ( 2004 { "m2m:cnt" ... } ) - ;; Provide extra requestVersionIndicator - (update-resource "CAdmin" "cse-in" { "m2m:cnt" : { "mni": 10 }} { "rvi": "3"}) ;; Returns ( 2004 { "m2m:cnt" ... } ) - ``` +;; Provide extra requestVersionIndicator +(update-resource "CAdmin" "cse-in" { "m2m:cnt" : { "mni": 10 }} { "rvi": "3"}) ;; Returns ( 2004 { "m2m:cnt" ... } ) +``` ## Text UI @@ -1715,10 +1627,9 @@ The function returns a list: The `open-web-browser` function opens a web browser with the given URL. -=== "Example" - ```lisp - (open-web-browser "https://www.onem2m.org") ;; Opens the web browser with the URL "https://www.onem2m.org" - ``` +```lisp title="Example" +(open-web-browser "https://www.onem2m.org") ;; Opens the web browser with the URL "https://www.onem2m.org" +``` --- @@ -1730,10 +1641,9 @@ The `set-category-description` function sets the description for a whole categor The description may contain Markdown formatting. -=== "Example" - ```lisp - (set-category-description "myCategory" "My category description") ;; Sets the description for the category "myCategory" - ``` +```lisp title="Example" +(set-category-description "myCategory" "My category description") ;; Sets the description for the category "myCategory" +``` --- @@ -1743,10 +1653,9 @@ The description may contain Markdown formatting. The `runs-in-tui` function determines whether the CSE currently runs in Text UI mode. -=== "Example" - ```lisp - (runs-in-tui) ;; Returns true if the CSE runs in Text UI mode - ``` +```lisp title="Example" +(runs-in-tui) ;; Returns true if the CSE runs in Text UI mode +``` --- @@ -1771,14 +1680,13 @@ This function is only available in TUI mode. It has the following arguments: If one of the optional arguments needs to be left out, a *nil* symbol must be used instead. The function returns NIL. -=== "Examples" - ```lisp - (tui-notify "a message") ;; Displays "a message" in an information notification for 3 seconds - (tui-notify "a message" "a title") ;; Displays "a message" with title "a title in an information notification for 3 seconds - (tui-notify "a message") ;; Displays "a message" in an information notification for 3 seconds - (tui-notify "a message" nil "warning") ;; Displays "a message" in a warning notification, no title - (tui-notify "a message" nil nil 10) ;; Displays "a message" in an information notification, no title, for 3 seconds - ``` +```lisp title="Examples" +(tui-notify "a message") ;; Displays "a message" in an information notification for 3 seconds +(tui-notify "a message" "a title") ;; Displays "a message" with title "a title in an information notification for 3 seconds +(tui-notify "a message") ;; Displays "a message" in an information notification for 3 seconds +(tui-notify "a message" nil "warning") ;; Displays "a message" in a warning notification, no title +(tui-notify "a message" nil nil 10) ;; Displays "a message" in an information notification, no title, for 3 seconds +``` --- @@ -1788,10 +1696,9 @@ The function returns NIL. The `tui-refresh-resources` function refreshes the resources in the CSE's Text UI. -=== "Example" - ```lisp - (tui-refresh-resources) ;; Refreshes the resource tree - ``` +```lisp title="Example" +(tui-refresh-resources) ;; Refreshes the resource tree +``` --- @@ -1801,10 +1708,10 @@ The `tui-refresh-resources` function refreshes the resources in the CSE's Text U The `tui-visual-bell` function shortly flashes the script's entry in the scripts' list/tree. -=== "Example" - ```lisp - (tui-visual-bell) ;; Flashes the script's name - ``` +```lisp title="Example" +(tui-visual-bell) ;; Flashes the script's name +``` + ## Network @@ -1829,22 +1736,21 @@ The function returns a list: - *response body* is the response content - *response headers* is a list of header fields. The format of these header fields is the same as in the request above. -=== "Examples" - ```lisp - ;; Retrieve a web page - (http 'get "https://www.onem2m.org") +```lisp title="Examples" +;; Retrieve a web page +(http 'get "https://www.onem2m.org") - ;; Send a oneM2M CREATE request manually - (http 'post "http://localhost:8080/cse-in" ;; Operation and URL - { "X-M2M-RI":"1234", ;; Header fields - "X-M2M-RVI": "4", - "X-M2M-Origin": "CAdmin", - "Content-type": "application/json;ty=3" } - { "m2m:cnt": { ;; Body - "rn": "myCnt" - ... - }}) - ``` +;; Send a oneM2M CREATE request manually +(http 'post "http://localhost:8080/cse-in" ;; Operation and URL + { "X-M2M-RI":"1234", ;; Header fields + "X-M2M-RVI": "4", + "X-M2M-Origin": "CAdmin", + "Content-type": "application/json;ty=3" } + { "m2m:cnt": { ;; Body + "rn": "myCnt" + ... + }}) +``` --- @@ -1862,11 +1768,10 @@ It has the following arguments: The function returns a boolean value. -=== "Examples" - ```lisp - (ping-tcp-service "localhost" 8080) ;; Returns true if the service is reachable - (ping-tcp-service "localhost" 8080 2) ;; Returns true if the service is reachable. Timeout after 2 seconds. - ``` +```lisp title="Examples" +(ping-tcp-service "localhost" 8080) ;; Returns true if the service is reachable +(ping-tcp-service "localhost" 8080 2) ;; Returns true if the service is reachable. Timeout after 2 seconds. +``` ## Provided Functions @@ -1874,10 +1779,9 @@ In addition to the functions defined in this documentation, more functions are p These functions can be included and made available in own scripts with the [include-script](#include-script) function: -=== "Example" - ```lisp - (include-script "ASFunctions") - ``` +```lisp title="Example" +(include-script "ASFunctions") +``` ### cadr @@ -1888,10 +1792,9 @@ The `cadr` function returns the second element of a list. !!! see-also "See also" [caddr](#caddr) -=== "Example" - ```lisp - (cadr '(1 2 3)) ;; Returns 2 - ``` +```lisp title="Example" +(cadr '(1 2 3)) ;; Returns 2 +``` --- @@ -1904,10 +1807,9 @@ The `caddr` function returns the third element of a list. !!! see-also "See also" [cadr](#cadr) -=== "Example" - ```lisp - (caddr '(1 2 3)) ;; Returns 3 - ``` +```lisp title="Example" +(caddr '(1 2 3)) ;; Returns 3 +``` --- @@ -1927,10 +1829,9 @@ The function returns the previous value of the configuration setting. !!! see-also "See also" [restore-config-value](#restore-config-value) -=== "Example" - ```lisp - (set-and-store-config-value "cse.checkExpirationsInterval" 10) ;; Returns the previous value of the configuration setting - ``` +```lisp title="Example" +(set-and-store-config-value "cse.checkExpirationsInterval" 10) ;; Returns the previous value of the configuration setting +``` --- @@ -1947,10 +1848,9 @@ The function has the following arguments: !!! see-also "See also" [set-and-store-config-value](#set-and-store-config-value) -=== "Example" - ```lisp - (restore-config-value "cse.checkExpirationsInterval") ;; Restores the configuration setting - ``` +```lisp title="Example" +(restore-config-value "cse.checkExpirationsInterval") ;; Restores the configuration setting +``` --- @@ -1969,10 +1869,9 @@ The function returns the response status. !!! see-also "See also" [get-response-resource](#get-response-resource) -=== "Example" - ```lisp - (get-response-status (retrieve-resource "CAdmin" "cse-in/myCnt")) ;; Returns the response status - ``` +```lisp title="Example" +(get-response-status (retrieve-resource "CAdmin" "cse-in/myCnt")) ;; Returns the response status +``` --- @@ -1991,10 +1890,9 @@ The function returns the response resource. !!! see-also "See also" [get-response-status](#get-response-status) -=== "Example" - ```lisp - (get-response-resource (retrieve-resource "CAdmin" "cse-in/myCnt")) ;; Returns the response resource - ``` +```lisp title="Example" +(get-response-resource (retrieve-resource "CAdmin" "cse-in/myCnt")) ;; Returns the response resource +``` --- @@ -2015,20 +1913,10 @@ The function has the following arguments: The function returns the result of the evaluated command. -=== "Example" - ```lisp - (eval-if-resource-exists "CAdmin" "cse-in/myCnt" (print "Resource exists") (print "Resource does not exist")) ;; Evaluates the command - ``` - - -;; Run a command if the resource exists and can be retrieved -;; Otherwise run the "else-cmd" command. -;; If found, the resource is stored in the "_resource" variable -;; that can be used in the "cmd" command. -(defun eval-if-resource-exists (originator id cmd else-cmd) - ( (setq response (retrieve-resource originator id)) - (if (== (get-response-status response) 2000) - ( (setq _resource (get-response-resource response)) - (eval cmd)) - (eval else-cmd)))) +```lisp title="Example" +(eval-if-resource-exists "CAdmin" + "cse-in/myCnt" + (print "Resource exists") + (print "Resource does not exist")) ;; Evaluates the command +``` diff --git a/docs/docs/development/ACMEScript-metatags.md b/docs/docs/development/ACMEScript-metatags.md index 136b83b8..3697790c 100644 --- a/docs/docs/development/ACMEScript-metatags.md +++ b/docs/docs/development/ACMEScript-metatags.md @@ -12,11 +12,10 @@ Meta tags are keywords that start with an at-character `@`. They can appear anyw Meta tags are added as constants to the script's environment and are prefixed with `meta.`. They can be accessed like any other environment variable, for example: -=== "Accessing a Meta Tag" - ```lisp - (if (is-defined 'meta.name) ;; note the quote in front of meta.name to prevent evaluation - (print "Script name:" meta.name)) ;; prints the script's name - ``` +```lisp title="Accessing a Meta Tag" +(if (is-defined 'meta.name) ;; note the quote in front of meta.name to prevent evaluation + (print "Script name:" meta.name)) ;; prints the script's name +``` ## Basic Meta Tags @@ -37,14 +36,13 @@ Each field is mandatory and must comply to the following values: - `- - ``` +```lisp title="Example" +@usage exampleScript +``` --- @@ -309,10 +294,9 @@ A category name for the script. This is used, for example, in the text UI tools !!! see-also [@name](#name), [@tool](#tool) -=== "Example" - ```lisp - @categoy System - ``` +```lisp title="Example" +@categoy System +``` --- @@ -328,10 +312,9 @@ When the *interval* argument is present it must be a positive float number that If this meta tag is present, with or without the *interval* argument, the environment variable `tui.autorun` is set to *true* when the script is run. -=== "Example" - ```lisp - @tuiAutoRun 10 - ``` +```lisp title="Example" +@tuiAutoRun 10 +``` --- @@ -347,10 +330,9 @@ The following configurations are possible: - Present in a script with an argument: The argument is used for the button's label. - Present in a script with no argument: The button is hidden. -=== "Example" - ```lisp - @tuiExecuteButton A Label - ``` +```lisp title="Example" +@tuiExecuteButton A Label +``` --- @@ -366,10 +348,9 @@ The following configurations are possible: - Not present in a script or without a label: No input field is added. - Present in a script with an argument: The argument is used for the input field's label. -=== "Example" - ```lisp - @tuiInput A Label - ``` +```lisp title="Example" +@tuiInput A Label +``` --- @@ -381,10 +362,9 @@ With this meta tag one can specify the sort order of a script in the Text UI's * The default sort order is 500. Scripts with a lower priority number are listed first. Scripts with the same priority are sorted alphabetically. -=== "Example" - ```lisp - @tuiSortOrder 100 - ``` +```lisp title="Example" +@tuiSortOrder 100 +``` --- @@ -394,7 +374,6 @@ The default sort order is 500. Scripts with a lower priority number are listed f This meta tag categorizes a script as a tool. Scripts marked as *tuiTools* are listed in the Text UI's *Tools* section. -=== "Example" - ```lisp - @tuiTool - ``` +```lisp title="Example" +@tuiTool +``` diff --git a/docs/docs/development/ACMEScript-operations.md b/docs/docs/development/ACMEScript-operations.md index b0331063..2c4137c4 100644 --- a/docs/docs/development/ACMEScript-operations.md +++ b/docs/docs/development/ACMEScript-operations.md @@ -15,20 +15,18 @@ The following comparison operations are supported by ACMEScript. They are used l | > | Greater than | `(> a b) ;; equal to: a > b` | | >= | Greater or equal than | `(>= a b) ;; equal to: a >= b` | -=== "Example" - ```lisp - (if (< 1 2) ;; Evaluates to "true" - (print "true") ;; This expression is executed - (print "false")) ;; This expression is not executed - ``` +```lisp title="Example" +(if (< 1 2) ;; Evaluates to "true" + (print "true") ;; This expression is executed + (print "false")) ;; This expression is not executed +``` !!! note The first operant in comparison operations may be a list or a quoted list. Only if the second operant is not a list, too, then the comparison operation is repeated for every member in the first operant's list. The comparison operation evaluates to *true* if any of these comparisons returns *true*. -=== "Example" - ```lisp - (== '(1 2 3) 2) ;; Evaluates to "true" - ``` +```lisp title="Example" +(== '(1 2 3) 2) ;; Evaluates to "true" +``` ## Logical Operations @@ -41,20 +39,18 @@ The following logical operations are supported by ACMEScript. They are used like | and, & | logical *and *of two or more boolean expressions | (and a b c) ;; a and b and c | | not, ! | logical negation or one boolean expression | (not true) ;; false | -=== "Example" - ```lisp - (or (< 1 2) (>= 4 3) (== 1 1)) ;; Returns true - (and (or true false) (not true)) ;; Returns false - ``` +```lisp title="Example" +(or (< 1 2) (>= 4 3) (== 1 1)) ;; Returns true +(and (or true false) (not true)) ;; Returns false +``` !!! note The first operant in logical operations may be a list or quoted list. Only if the second operant is not a list, too, then the logical operation is repeated for every member in the first operant's list. The logical operation evaluates to *true* if any of these operations returns *true*. -=== "Example" - ```lisp - (and '(false false true) true) ;; Evaluates to "true" - (and '(false false false) true) ;; Evaluates to "false" - ``` +```lisp title="Examples" +(and '(false false true) true) ;; Evaluates to "true" +(and '(false false false) true) ;; Evaluates to "false" +``` ## Mathematical Operations @@ -70,8 +66,7 @@ The following mathematical operations are supported by ACMEScript. They are used | ** | Calculates the power of two or more numbers | (** 2 3 4) ;; Returns 4096 | | % | Calculates to modulo of two or more numbers | (% 100 21 13) ;; Returns 3 | -=== "Example" - ```lisp - (* 6 7) ;; Returns 42 - (* (+ 3 3) 7) ;; Return 42 - ``` +```lisp title="Examples" +(* 6 7) ;; Returns 42 +(* (+ 3 3) 7) ;; Return 42 +``` diff --git a/docs/docs/development/ACMEScript-uppertester.md b/docs/docs/development/ACMEScript-uppertester.md index e52e3a1b..976023a7 100644 --- a/docs/docs/development/ACMEScript-uppertester.md +++ b/docs/docs/development/ACMEScript-uppertester.md @@ -2,12 +2,15 @@ ACMEScript is integrated with the [Upper Tester (UT) Interface](../setup/Operation.md#upper_tester). To enable this a script must have the [@uppertester](ACMEScript-metatags.md#uppertester) meta tag set. It can then be run through the UT interface by having its [@name](ACMEScript-metatags.md#name) (and optional script arguments) as the parameter of the upper tester's *X-M2M-UTCMD* header field of a http request: -```text +```text title="Upper Tester Request" X-M2M-UTCMD: aScript param1 param2 ``` A script result is passed back in a response in the *X-M2M-UTRSP* header of the response: -```text +```text title="Upper Tester Response" X-M2M-UTRSP: aResult -``` \ No newline at end of file +``` + +!!! see-also + [Upper Tester Interface](../setup/Operation-UpperTester.md) \ No newline at end of file diff --git a/docs/docs/development/ACMEScript-variables.md b/docs/docs/development/ACMEScript-variables.md index 53b83a11..c77366ec 100644 --- a/docs/docs/development/ACMEScript-variables.md +++ b/docs/docs/development/ACMEScript-variables.md @@ -11,12 +11,11 @@ Evaluates to the number of elements in [argv](../development/ACMEScript-function !!! see-also "See also" [argv](../development/ACMEScript-functions.md#argv) -=== "Example" - ```lisp - (if (> argc 2) - ((log-error "Wrong number of arguments") - (quit-with-error))) - ``` +```lisp title="Example" +(if (> argc 2) + ((log-error "Wrong number of arguments") + (quit-with-error))) +``` --- @@ -24,7 +23,7 @@ Evaluates to the number of elements in [argv](../development/ACMEScript-function `event.data` -Evaluates to the payload data of an event. This could be, for example, the string representation in case of an [onKey](../development/ACMEScript.md#onkey) event. +Evaluates to the payload data of an event. This could be, for example, the string representation in case of an [onKey](../development/ACMEScript-metatags.md#onkey) event. !!! note This variable is only set when the script was invoked by an event. @@ -32,11 +31,10 @@ Evaluates to the payload data of an event. This could be, for example, the strin !!! see-also "See also" [event.type](#eventtype) -=== "Example" - ```lisp - (if (== event.type "onKey") ;; If the event is "onKey" - (print "Key:" event.data)) ;; Print the pressed key - ``` +```lisp title="Example" +(if (== event.type "onKey") ;; If the event is "onKey" + (print "Key:" event.data)) ;; Print the pressed key +``` --- @@ -44,7 +42,7 @@ Evaluates to the payload data of an event. This could be, for example, the strin `event.type` -Evaluates to the type of an event. This could be, for example, the value *"onKey"* in case of an [onKey](../development/ACMEScript.md#onkey)* event. +Evaluates to the type of an event. This could be, for example, the value *"onKey"* in case of an [onKey](../development/ACMEScript-metatags.md#onkey) event. !!! note This variable is only set when the script was invoked by an event. @@ -52,9 +50,7 @@ Evaluates to the type of an event. This could be, for example, the value *"onKey !!! see-also "See also" [event.data](#eventdata) -Example: - -```lisp +```lisp title="Example" (if (== event.type "onKey") ;; If the event is "onKey" (print "Key:" event.data)) ;; Print the pressed key ``` @@ -69,10 +65,9 @@ The `notification.originator` variable is set when a script is called to process It contains the notification's originator. -=== "Example" - ```lisp - (print notification.originator) - ``` +```lisp title="Example" +(print notification.originator) +``` --- @@ -84,10 +79,9 @@ The `notification.resource` variable is set when a script is called to process a It contains the notification's JSON body. -=== "Example" - ```lisp - (print notification.resource) - ``` +```lisp title="Example" +(print notification.resource) +``` --- @@ -99,10 +93,9 @@ The `notification.uri` variable is set when a script is called to process a noti It contains the notification's target URI. -=== "Example" - ```lisp - (print notification.uri) - ``` +```lisp title="Example" +(print notification.uri) +``` --- @@ -118,12 +111,11 @@ Evaluates to *true* if the script was started as an "autorun" script. This is th !!! note This variable is only set when the script is run from the text UI. -=== "Example" - ```lisp - (if (is-defined 'tui.autorun) ;; If the variable is defined - (if (== tui.autorun true) ;; If the script is an autorun script - (print "Autorun: True"))) ;; Print a message - ``` +```lisp title="Example" +(if (is-defined 'tui.autorun) ;; If the variable is defined + (if (== tui.autorun true) ;; If the script is an autorun script + (print "Autorun: True"))) ;; Print a message +``` --- @@ -133,8 +125,7 @@ Evaluates to *true* if the script was started as an "autorun" script. This is th Evaluates to the state of the current theme of the text UI. This can either be the values *light* or *dark*. -=== "Example" - ```lisp - (print "Theme: " tui.theme) ;; Print the theme name - ``` +```lisp title="Example" +(print "Theme: " tui.theme) ;; Print the theme name +``` diff --git a/docs/docs/development/ACMEScript.md b/docs/docs/development/ACMEScript.md index 0e9d248e..897d6031 100644 --- a/docs/docs/development/ACMEScript.md +++ b/docs/docs/development/ACMEScript.md @@ -17,15 +17,14 @@ The scripts are stored as normal text files. A script contains so-called [s-expr An s-expression is a list of symbols that represent either a value or another s-expression. Usually, the first element in the list is the function that is called to perform a function, and that may have zero, one, or multiple symbols as arguments. If such an argument symbol is executable, then it is recursively evaluated, and its result is taken as the actual argument. -=== "Example" - ```lisp - ;; Print "Hello, World" to the console - (print "Hello, World!") +```lisp title="Example" +;; Print "Hello, World" to the console +(print "Hello, World!") - ;; Print the result of calculations to the console - (print (+ 1 2)) ;; prints 3 - (print (+ 1 (/ 8 4))) ;; prints 3 - ``` +;; Print the result of calculations to the console +(print (+ 1 2)) ;; prints 3 +(print (+ 1 (/ 8 4))) ;; prints 3 +``` ### Data Types @@ -44,12 +43,11 @@ The following data types are supported by ACMEscript: Every function and s-expression returns a value. This is usually the function result, but when a list contains multiple s-expressions that are evaluated, then only the last s-expression's result is returned. -=== "Example" - ```lisp - ;; First, set the variable a to 3, then use it in a calculation. - ;; Then, the result of the calculation is printed. - (print ( (setq a 3) (+ a 4) )) ;; prints 7 - ``` +```lisp title="Example" +;; First, set the variable a to 3, then use it in a calculation. +;; Then, the result of the calculation is printed. +(print ( (setq a 3) (+ a 4) )) ;; prints 7 +``` ### Variables and Function Scopes @@ -77,23 +75,21 @@ It doesn't matter whether a symbol is another s-expression, a built-in, self-def Some functions assume that one or more arguments are implicitly quoted, such as the *setq* function that doesn't evaluating its first argument. In this case the argument is not quoted. -=== "Example" - ```lisp - ;; Print the string "(+ 1 2)" to the console - (print '(+ 1 2)) +```lisp title="Example" +;; Print the string "(+ 1 2)" to the console +(print '(+ 1 2)) - ;; Set a variable "a" to 42 and print the variable to the console - (setq a 42) ;; a is not evaluated! - (print a) ;; a is evaluated. It prints 42 - ``` +;; Set a variable "a" to 42 and print the variable to the console +(setq a 42) ;; a is not evaluated! +(print a) ;; a is evaluated. It prints 42 +``` Sometimes it is not possible to quote an s-expression or symbol because it is the result of the evaluation of another s-expression. In this case the [quote](ACMEScript-functions.md#quote) function can be used to return a quoted version of an s-expression. -=== "Example" - ```lisp - ;; Print the string "(+ 1 2)" to the console - (print (quote (+ 1 2))) - ``` +```lisp title="Example" +;; Print the string "(+ 1 2)" to the console +(print (quote (+ 1 2))) +``` ### Meta Tags @@ -112,7 +108,7 @@ Data can be stored "persistently" during a CSE's runtime. This is intended to pa To store data persistently one may consider to store it in the oneM2M resource tree. -See: [get-storage](ACMEScript-functions.md#get-storage), [has-storage](ACMEScript-functions.md#has-storage), [set-storage](ACMEScript-functions.md#set-storage) +See: [get-storage](ACMEScript-functions.md#get-storage), [has-storage](ACMEScript-functions.md#has-storage), [put-storage](ACMEScript-functions.md#put-storage) ### Evaluating S-Expressions in Strings and JSON Structures @@ -121,17 +117,15 @@ S-expressions that are enclosed in the pattern `${..}` in a string or JSON struc In the following example the s-expression `(+ 1 2)` is evaluated when the string is processed: -=== "Example" - ```lisp - (print "1 + 2 = ${ + 1 2 }") ;; Prints "1 + 2 = 3" - ``` +```lisp title="Example" +(print "1 + 2 = ${ + 1 2 }") ;; Prints "1 + 2 = 3" +``` Evaluation can be locally disabled by escaping the opening part: -=== "Example" - ```lisp - (print "1 + 2 = \\${ + 1 2 }") ;; Prints "1 + 2 = ${ + 1 2 )}" - ``` +```lisp title="Example" +(print "1 + 2 = \\${ + 1 2 }") ;; Prints "1 + 2 = ${ + 1 2 )}" +``` Evaluation can also be disabled and enabled by using the [evaluate-inline](ACMEScript-functions.md#evaluate-inline) function. @@ -143,22 +137,22 @@ If the function `on-error` is defined in a script, then this function is execute In general, the `on-error` function is called as follows: -=== "on-error" - `(on-error ] + + // The attribute's short name. + // Mandatory. + "sname" : "attributeShortName", + + // The attribute's long name. + // Optional, and for future developments. + "lname" : "attributeLongName", + + // The attribute's namespace. + // Optional, the default is "m2m". + "ns" : "namespace", + + // The attribute's data type. + // Mandatory, and one from this list: + // + // - integer + // - positiveInteger + // - nonNegInteger + // - unsignedInt + // - unsignedLong + // - string + // - timestamp + // - list + // - dict - any anonymous complex structure. This should be avoided and + // be replaced by a complex type name + // - adict (anonymous dict) + // - anyURI + // - boolean + // - enum + // - geoCoordinates + // - schedule + // - base64 + // - duration + // + // In addition to the list above, the *attributeType* can be the name of any defined + // complex type. This complex type must be defined in any of the attribute policy files. + "type" : "attributeType", + + // The sub-type of a list type. + // This can be any of the types defined for *type*, or a complex type. + "ltype" : "type", + + // The complex type name for a complex type attribute. + // This is the name of the parent complex type to which an attribute belongs. + // This attribute is only present in an attribute policy definition when + // this attribute belongs to a complex type. + "ctype" : "complexType", + + // Definition of enumeration values. + // This can only be an integer value, or range definitions in the format + // "start..end" that evaluate to all the integer values of the given range. + "evalues" : [ 1, 2, "3..5", 6 ], + + // Definition of an enumeration type and an alternative to "evalues". + // This is an enumerated data type name that is referenced. + "etype" : "enumeratedDataType", + + // The "oc" field specifies the CREATE request optionality. Optional, and one from this list: + // - O : Optional provided (default) + // - M : Mandatory provided + // - NP : Not provided + "oc" : "O|M|NP", + + // The "ou" field specifies the UPDATE request optionality. + // Optional, and one from this list: + // + // - O : Optional provided (default) + // - M : Mandatory provided + // - NP : Not provided + "ou" : "O|M|NP", + + // The "od" field specifies the DISCOVERY request optionality. + // Optional, and one from this list: + // + // - O : Optional provided (default) + // - M : Mandatory provided + // - NP : Not provided + "od" : "O|M|NP", + + // The "annc" field specifies whether an announced optionality. + // Optional, and one from this list: + // + // - OA : Optional announced (default) + // - MA : Mandatory announced + // - NA : Not announced + "annc": "OA|MA|NA", + + // The attribute multiplicity. + // Optional, and one from this list: + // + // - 01 : The attribute is optional (default) + // - 01L : the attribute is an optional list + // - 1 : The attribute is mandatory + // - 1L : The attribute is a mandatory list + "car" : "01|01L|1|1L", +} +``` + +## Complex Types + +Complex types are defined in the attribute policy files as well. Complex types are defined in files with the extension `.ap`. The CSE reads the complex type definitions from the [init](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init){target=_new} and [secondary init](../setup/Running.md#secondary-init-directory) directory, for example [complexTypePolicies.ap](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init/complexTypePolicies.ap){target=_new}. More than one complex type file can be provided. + +Complex types are defined indirectly by assigning attributes to them. Attributes for a complex types are defined in the same way as for common resource types. The only difference is that the *ctype* field is set to the name of the complex type the attribute belongs to. If an attribute belongs to more than one complex type, the attribute definition is repeated for each complex type. + +The *rtypes* field must by set to "COMPLEX" for complex type attributes. + +The following example shows the definition of an attribute that belongs to multiple complex types. + +```json title="Complex Type Attribute Example" + "dur": [ + // This attribute is defined for the complex type m2m:batchNotify + { + "rtypes": [ "COMPLEX" ], + "ctype": "m2m:batchNotify", + "lname": "duration", + "ns": "m2m", + "type": "duration", + "car": "01" + }, + + // This attribute is defined for the complex type m2m:misingData + { + "rtypes": [ "COMPLEX" ], + "ctype": "m2m:missingData", + "lname": "duration", + "ns": "m2m", + "type": "duration", + "car": "1" + } + ] +``` + + +## Enumeration Data Types + +In addition to the attribute and complex type policies defined above, enumeration types are defined in files with the extension `.ep`. The CSE reads the enumeration data types from the [init](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init){target=_new} and [secondary init](../setup/Running.md#secondary-init-directory) directory, for example [enumTypesPolicies.ep](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init/enumTypesPolicies.ep){target=_new}. More than one enumeration data type file can be provided. + +The format is a JSON structure that follows the structure described in the following code. + +```json title="Enumeration Data Type Format" +// The attributePolicy.ep file contains a dictionary of enumeration data types +{ + + // Each enumeration definition is identified by its name. + It is a dictionary. + "enumerationType": { + + // An enumeration definition is key value pair. + // The key is the enumeration value (usually an integer), + // and the value is the interpretation of that value. + // This entry can be repeated for each enumeration value. + "" : "" + } +} +``` + +The following example show the definition for an enumeration data type. + +```json title="Enumeration Data Type Example" +{ + "m2m:resultContent" : { + "0": "Nothing", + "1": "Attributes", + "2": "Hierarchical address", + "3": "Hierarchical address and attributes", + "4": "Attributes and child resources", + "5": "Attributes and child resource references", + "6": "Child resource references", + "7": "Original resource", + "8": "Child resources", + "9": "Modified attributes", + "10": "Semantic content", + "11": "Semantic content and child resources", + "12": "Permissions" + }, +} +``` + +**Example** + +The following gives an example for the attribute *ty* (*resourceType*). + +```jsonc +{ + "rn": [ + { + "rtypes": [ "ALL" ], + "lname": "resourceName", + "ns": "m2m", + "type": "string", + "car": "1", + "oc": "O", + "ou": "NP", + "od": "O", + "annc": "NA" + } + ], + "ty": [ + { + "rtypes": [ "ALL" ], + "lname": "resourceType", + "ns": "m2m", + "type": "enum", + "etype": "m2m:resourceType", + "car": "1", + "oc": "NP", + "ou": "NP", + "od": "O", + "annc": "NA" + } + ] +} +``` + +**Complex Type Attribute** + +The following example shows the definition of the attribute *operator* (optr) that +belongs to the complex type *m2m:evalCriteria*. + +```jsonc +{ + "optr": [ + { + "rtypes": [ "COMPLEX" ], + "ctype": "m2m:evalCriteria", + "lname": "operator", + "ns": "m2m", + "type": "enum", + "etype": "m2m:evalCriteriaOperator", + "car": "1" + } + ] +} +``` diff --git a/docs/docs/development/DebugMode.md b/docs/docs/development/DebugMode.md new file mode 100644 index 00000000..91a3a44e --- /dev/null +++ b/docs/docs/development/DebugMode.md @@ -0,0 +1,12 @@ +# Debug Mode + +The CSE tries to catch errors and give helpful advice as much as possible during runtime. +However, there are circumstances when this could not done easily, e.g. during startup. + +In order to provide additional information in these situations one can set the *ACME_DEBUG* environment variable (to any value): + +```sh title="Set the ACME_DEBUG environment variable" +export ACME_DEBUG=1 +``` + +Then run the CSE as usual. \ No newline at end of file diff --git a/docs/docs/development/FlexContainerPolicies.md b/docs/docs/development/FlexContainerPolicies.md new file mode 100644 index 00000000..35e824bc --- /dev/null +++ b/docs/docs/development/FlexContainerPolicies.md @@ -0,0 +1,127 @@ +# FlexContainer Specifalization Policies + +This article describes the flexContainer specialization policies used by the ACME CSE. + +For all <flexContainer> specializations, e.g. for oneM2M's TS-0023 ModuleClasses, the [attribute policies](../development/AttributePolicies.md) and the allowed <flexContainer> hierarchy must be provided. + +The files for <flexContainer> specializations are also imported from the [init](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init){target=_new} and [secondary init](../setup/Running.md#secondary-init-directory) directory. More than one such file can be provided, for example one per domain. The files must have the extension `.fcp`. + +The format is a JSON structure that follows the structure described in the following code. + +```json title="FlexContainer Specialization Policy Format" +[ +// A file contains a list of FlexContainer Specialization Policies +specializationPolicy = [ + + // Each FlexContainer Specialization Policies is a JSON object + { + // The specialisation's namespace and short name. + // Mandatory. + "type" : "namespace:shortname", + + // The specialisation's long name. + // Optional, and for future developments. + "lname" : "attributePolicyLongname", + + // The specialisation's containerDefinition. + // Mandatory for flexContainers, but can be empty to prevent warnings. + "cnd" : "containerDefinition", + + // The specialisation's SDT type. + // Could be one of "device", "subdevice", "moduleclass", or "action". + // Optional, and for future developments. + "sdttype" : "SDTcontainerType", + + // A list of attribute policies. + // Each entry specifies a single attribute of the specialization. + // Optional. + "attributes": [ attributePolicy, attributePolicy, ... ], + + // A list of child resource types. Optional. + "children" : [ + // This list consists of one or more strings, each of those is the name of an additional + // child resource specialisation. It is not necessary to specify here the already allowed + // child resource types of . + ] + }] +] +``` +The *attributePolicies* are the same as described in the [Attribute Policies](../development/AttributePolicies.md) article. + +## Examples + +The following examples show the attribute policies for the *binarySwitch* and *deviceLight* specialisations, both defined in oneM2M's TS-0023 specification. + +```json title="FlexContainer specialization binarySwitch.fcp" +[ + // ModuleClass: binarySwitch (binSh) + { + "type" : "cod:binSh", + "lname" : "binarySwitch", + "cnd" : "org.onem2m.common.moduleclass.binarySwitch", + "attributes": [ + // DataPoint: dataGenerationTime + { + "sname" : "dgt", + "lname" : "dataGenerationTime", + "type" : "timestamp", + "car" : "01" + }, + + // DataPoint: powerState + { + "sname" : "powSe", + "lname" : "powerState", + "type" : "boolean", + "car" : "1" + } + ] + } +] +``` + +```json title="deviceLight.fcp" +[ + // ModuleClass: binarySwitch (binSh) + { + "type" : "cod:binSh", + "lname" : "binarySwitch", + "cnd" : "org.onem2m.common.moduleclass.binarySwitch", + "attributes": [ + // DataPoint: dataGenerationTime + { + "sname" : "dgt", + "lname" : "dataGenerationTime", + "type" : "timestamp", + "car" : "01" + }, + + // DataPoint: powerState + { + "sname" : "powSe", + "lname" : "powerState", "type" : + "boolean", + "car" : "1" + } + ] + }, + + // DeviceClass: deviceLight + { + "type" : "cod:devLt", + "lname" : "deviceLight", + "cnd" : "org.onem2m.common.device.deviceLight", + + // The allowed child resource types + "children" : [ + "cod:fauDn", + "cod:binSh", + "cod:runSe", + "cod:color", + "cod:colSn", + "cod:brigs" + ] + } +] +``` + diff --git a/docs/docs/development/HelpDocumentation.md b/docs/docs/development/HelpDocumentation.md new file mode 100644 index 00000000..fb93c092 --- /dev/null +++ b/docs/docs/development/HelpDocumentation.md @@ -0,0 +1,31 @@ +# Help File Format + + +Some CSE UI components provide a markdown documentation to the user, such as the Text UI. That documentation is imported from the [init](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init){target=_new} directory as well. The file extension for documentation files is `.docmd`. + +## Format + +In the documentation file individual sections are separated by markdown level-1 headers where the header title is the help topic for the following section, which is markdown text with the acual help text. + +## Examples + + +```markdown +# Topic 1 + +Some help text for topic 1 in markdown format. + +## Help sub section that belongs to topic 1 + +Some help text for the sub section in markdown format. + +# Topic 2 + +Another help text for topic 2 in markdown format. + +... + +``` + + + diff --git a/docs/docs/development/Integrating_ACME.md b/docs/docs/development/Integrating_ACME.md new file mode 100644 index 00000000..2fabee05 --- /dev/null +++ b/docs/docs/development/Integrating_ACME.md @@ -0,0 +1,46 @@ +# Integrating Into Other Projects + +This article describes how to integrate the CSE into other applications and how to run it in a Jupyter Notebook. + +## Introduction + +It is possible to integrate the CSE into other applications. In this case you would possibly like to provide startup arguments, for example the path of the configuration file or the logging level, directly instead of getting them from the command line via *argparse*. + +You might want to get the example from the main file [acme/\_\_main__.py](https://github.com/ankraft/ACME-oneM2M-CSE/tree/master/acme/__main__.py) where you could replace the line: + +```python title="Replace this line from the main file" +CSE.startup(parseArgs()) +``` + +with a call to the CSE's *startup()* function for your application: + +```python title="Call the CSE's startup function" +CSE.startup(None, configfile=defaultConfigFile, loglevel='error') +``` + +!!! note + The first argument of the *startup()* function is the *argparse* arguments. In case you provide the arguments directly the first argument may need to be `None`. + +The names of the *argparse* variables can be used here, and you may provide all or only some of the arguments. Please note that you need to keep or copy the `import` and `sys.path` statements at the top of that file. + + +### Jupyter Notebooks + +Since ACME CSE is written in pure Python it can be run in a Jupyter Notebook. The following code could be copied to a notebook cell to run the CSE. + +```python title="Run the CSE in a Jupyter Notebook" +# Increase the width of the notebook to accommodate the log output +from IPython.display import display, HTML +display(HTML("")) + +# Change to the CSE's directory and start the CSE +# Ignore the error from the %cd command +%cd -q tools/ACME # adopt this to the location of the ACME CSE +%run -m acme -- --headless +``` + +Note the following: + +- The CSE should be run in *headless* mode to avoid too much output to the notebook. +- Once executed the notebook cell will not finish its execution. It is therefore recommended to run the CSE in a separate notebook. +- The CSE can only be stopped by stopping or restarting the notebook's Python kernel. diff --git a/docs/docs/development/NotificationServer.md b/docs/docs/development/NotificationServer.md new file mode 100644 index 00000000..959215a7 --- /dev/null +++ b/docs/docs/development/NotificationServer.md @@ -0,0 +1,61 @@ +# Notification Server + +The Notification that is provided in the directory [tools/notificationServer](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/tools/notificationServer){target=_new} is a simple implementation of a Notification Server that can be used to receive notifications from a CSE. + +Verification requests are always acknowledged, and notifications are just printed to the console. + +The server normally listens on port `9999`. This can be changed by specifying another value to the *port* variable at the beginning of the file or the command line argumnet *--port* (see below). + +## Running + +### Start as basic HTTP notification server +Start the server by running the command from the `tools/notificationServer` directory: + +```bash title="Start the Notification Server" +python3 notificationServer.py +``` + +In this case the starts and listens on the default port `9999` for incoming connections. + + +### Start with MQTT support + +The Notification Server can be started with MQTT support. In this case the server connects to an MQTT broker and listens to the topics specified in the command line arguments. + + +```bash title="Start the Notification Server" +python3 notificationServer.py --mqtt --mqtt-address mqtt.example.com +``` + +The following command starts the NotificationServer with MQTT support enabled. The server would connect to an MQTT broker with username and password authentication, and would listen to the topics "/oneM2M/req/id-in/+/#" and "/oneM2M/req/id-mn/+/#". In addition logging extra information about the MQTT communication to the console +is enabled. + +```bash title="More Sophisticated MQTT Example" +python3 notificationServer.py --mqtt --mqtt-address mqttAddress --mqtt-username mqttUser --mqtt-password mqttPassword --mqtt-useTLS --mqtt-caFile caFile --mqtt-certfile certFile -- mqtt-keyfile keyFile --mqtt-topic /oneM2M/req/id-in/+/# /oneM2M/req/id-mn/+/# --mqtt-logging +``` + + +## Command Line Arguments + +The Notification Server can be started with the following command line arguments: + +| Command Line Argument | Description | +|--------------------------------------------|----------------------------------------------------------------------| +| -h, --help | Show a help message and exit. | +| --port <port> | Specify the server port (default: 9999). | +| --http, --https | Run as http (default) or as https server. | +| --certfile <certfile> | Specify the certificate file (mandatory for https). | +| --keyfile <keyfile> | Specify the key file (mandatory for https). | +| --mqtt | Additionally enable MQTT for notifications | +| --mqtt-address <host> | MQTT broker address (default: localhost) | +| --mqtt-port <port> | MQTT broker port (default: 1883) | +| --mqtt-topic <topic> [<topic> ...] | MQTT topic list to subscribe to (default: ['/oneM2M/req/id-in/+/#']) | +| --mqtt-username <username> | MQTT username (default: None) | +| --mqtt-password <password> | MQTT password (default: None) | +| --mqtt-logging | MQTT enable logging (default: disabled) | +| --mqtt-useTLS | MQTT enable TLS (default: disabled) | +| --mqtt-caFile <cafile> | Specify the CA certificate file (mandatory for MQTTS) | +| --mqtt-certfile <certfile> | Specify the certificate file (mandatory for MQTTS) | +| --mqtt-keyfile <keyfile> | Specify the key file (mandatory for MQTTS) | +| --fail-verification | Fail all verification requests with "no privileges" (default: False) | +| --delay-response [<delay>] | Delay response by n seconds (default: 60s) | diff --git a/docs/docs/development/Overview.md b/docs/docs/development/Overview.md index ba8507a1..62380a3b 100644 --- a/docs/docs/development/Overview.md +++ b/docs/docs/development/Overview.md @@ -2,8 +2,6 @@ This article provides an overview of the ACME CSE's architecture, components, and database schemas. -TODO It also describes how to integrate the CSE into other applications and how to run it in a Jupyter Notebook. - ## Components The ACME CSE is divided into several components. The following diagram shows the components and their relationships. @@ -38,65 +36,3 @@ The database used by the CSE is [TinyDB](https://github.com/msiemens/tinydb){tar The filenames include the *CSE-ID* of the running CSE, so if multiple CSEs are running and are using the same data directory then they won't interfere with each other. The database files are copied to a *backup* directory at CSE startup. Some database tables duplicate attributes from actual resources, e.g. in the *subscription* database. This is mainly done for optimization reasons in order to prevent a retrieval and instantiation of a full resource when only a few attributes are needed. - - -TODO move the following to a separate article "Integrating - -## Integration Into Other Applications - -It is possible to integrate the CSE into other applications. In this case you would possibly like to provide startup arguments, for example the path of the configuration file or the logging level, directly instead of getting them from *argparse*. - -You might want to get the example from the starter file [acme.py](acme.py) where you could replace the line: - -```python -CSE.startup(parseArgs()) -``` - -with a call to the CSE's *startup()* function: - -```python -CSE.startup(None, configfile=defaultConfigFile, loglevel='error') -``` - -Please note that in case you provide the arguments directly the first argument needs to be `None`. - -The names of the *argparse* variables can be used here, and you may provide all or only some of the arguments. Please note that you need to keep or copy the `import` and `sys.path` statements at the top of that file. - -### Jupyter Notebooks - -Since ACME CSE is written in pure Python it can be run in a Jupyter Notebook. The following code could be copied to a notebook to run the CSE. - -```python -# Increase the width of the notebook to accommodate the log output -from IPython.display import display, HTML -display(HTML("")) - -# Change to the CSE's directory and start the CSE -# Ignore the error from the %cd command -%cd -q tools/ACME # adopt this to the location of the ACME CSE -%run -m acme -- --headless -``` - -- The CSE should be run in *headless* mode to avoid too much output to the notebook. -- Once executed the notebook cell will not finish its execution. It is therefore recommended to run the CSE in a separate notebook. -- The CSE can only be stopped by stopping or restarting the notebook's Python kernel. - - - - - -## MyPy Static Type Checker - -The CSE code is statically type-checked with [mypy](https://mypy-lang.org){target=_new}. - -Just execute the ```mypy``` command in the project's root directory. It will read its configuration from the configuration file [mypy.ini](../mypy.ini). - - -## Debug Mode - -The CSE tries to catch errors and give helpful advice as much as possible during runtime. -However, there are circumstances when this could not done easily, e.g. during startup. - -In order to provide additional information in these situations one can set the *ACME_DEBUG* environment variable (to any value): - - $ export ACME_DEBUG=1 \ No newline at end of file diff --git a/docs/docs/development/StartupResources.md b/docs/docs/development/StartupResources.md new file mode 100644 index 00000000..4ae6767f --- /dev/null +++ b/docs/docs/development/StartupResources.md @@ -0,0 +1,56 @@ +# Start-Up Resources + +This article describes the startup process of the CSE, how to import resources, and attribute definitions. + +## Initial Resources + +During CSE startup and restart it is necessary to import a first set of resources to the CSE. This is done automatically by the CSE by running a script that has the [@init](../development/ACMEScript-metatags.md#init) meta tag set. By default this is the [init.as](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init/init.as){target=_new} script from the [init](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init){target=_new} directory. + +Not much validation, access control, or registration procedures are performed when importing resources this way. + +!!! see-also "See also" + The meta tag [@init](../development/ACMEScript-metatags.md#init) + +### Adding Mandatory Resources to the CSE + +Please note that it is required for a functional CSE deployment to create the CSE's *CSEBase*, the administration *AE*, and a general-access *ACP* resources. Those are created before all other resources, so that the *CSEBase* resource can act as the root for the resource tree, and basic permissions are provided. + +### Adding other Resources + +An option to import more resources automatically whenever the CSE starts or restarts is to have a script as an event handler for the [onStartup](../development/ACMEScript-metatags.md#onstartup) and [onRestart](../development/ACMEScript-metatags.md#onrestart) events. + +These scripts can be added to the [Secondary *init* Directory](../setup/Running.md#secondary-init-directory), which is located in the base directory of the CSE, and from where resources are imported after the primary *init* directory has been processed. + +!!! see-also "See also" + The meta tags [onStartup](../development/ACMEScript-metatags.md#onstartup), [onRestart](../development/ACMEScript-metatags.md#onrestart) + [Secondary *init* Directory](../setup/Running.md#secondary-init-directory) + + +### Referencing Configuration Settings + +By using macros the initial resources can be kept independent from individual settings. +Most [configuration](../setup/Configuration-basic.md) settings can be added by macro replacement. +For this a given macro name is enclosed by `${...}`, e.g. `${cse.cseID}`. + +The following example shows the initial *CSEBase* resource definition from the [init.as](https://github.com/ankraft/ACME-oneM2M-CSE/blob/master/acme/init/init.as){target=_new} script file: + +```lisp title="init.as" +(import-raw + (get-config "cse.originator") ;(1)! + {"m2m:cb": { + "ri": "${ get-config \"cse.resourceID\" }", ;(2)! + "rn": "${ get-config \"cse.resourceName\" }", + "csi": "${ get-config \"cse.cseID\" }", + "rr": true, + "csz": [ "application/json", "application/cbor" ], + "acpi": [ "${ get-config \"cse.cseID\" }/acpCreateACPs" ], + "poa": [ "${ get-config \"http.address\" }" ] + }}) +``` + +1. The `get-config` function is used to retrieve the configuration setting for the CSE's originator. +2. The `${...}` macro is used to replace the configuration setting for the CSE's resource ID in a string. + +!!! see-also "See also" + [Evaluating S-Expressions in Strings and JSON Structures](../development/ACMEScript.md#evaluating-s-expressions-in-strings-and-json-structures) + diff --git a/docs/docs/development/TypeChecking.md b/docs/docs/development/TypeChecking.md new file mode 100644 index 00000000..da282e52 --- /dev/null +++ b/docs/docs/development/TypeChecking.md @@ -0,0 +1,11 @@ +# Type Checking + +The CSE code is statically type-checked with [mypy](https://mypy-lang.org){target=_new}. + +Just execute the `mypy` command in the project's root directory and make sure that the [mypy.ini](https://github.com/ankraft/ACME-oneM2M-CSE/tree/master/mypy.ini) file is present. The configuration file contains the setting for the type-checking process. + +```sh title="Run mypy in the project's root directory" +mypy +``` + + diff --git a/docs/docs/development/UnitTests.md b/docs/docs/development/UnitTests.md index 4f67abd8..9cc411ff 100644 --- a/docs/docs/development/UnitTests.md +++ b/docs/docs/development/UnitTests.md @@ -30,25 +30,24 @@ Each test suite imports the file [init.py](https://github.com/ankraft/ACME-oneM2 For each aspect of the CSE there is one test suite file that can be run independently or in the course of an overall test. For example, running the test suite for AE tests would look like this: -=== "Example Test Suite Run" - ```text - $ python3 testAE.py - test_createAE (__main__.TestAE) ... ok - test_createAEUnderAE (__main__.TestAE) ... ok - test_retrieveAE (__main__.TestAE) ... ok - test_retrieveAEWithWrongOriginator (__main__.TestAE) ... ok - test_attributesAE (__main__.TestAE) ... ok - test_updateAELbl (__main__.TestAE) ... ok - test_updateAETy (__main__.TestAE) ... ok - test_updateAEPi (__main__.TestAE) ... ok - test_updateAEUnknownAttribute (__main__.TestAE) ... ok - test_retrieveAEACP (__main__.TestAE) ... ok - test_deleteAEByUnknownOriginator (__main__.TestAE) ... ok - test_deleteAEByAssignedOriginator (__main__.TestAE) ... ok - ---------------------------------------------------------------------- - Ran 12 tests in 0.116s - OK - ``` +```text title="Example Test Suite Run" +$ python3 testAE.py +test_createAE (__main__.TestAE) ... ok +test_createAEUnderAE (__main__.TestAE) ... ok +test_retrieveAE (__main__.TestAE) ... ok +test_retrieveAEWithWrongOriginator (__main__.TestAE) ... ok +test_attributesAE (__main__.TestAE) ... ok +test_updateAELbl (__main__.TestAE) ... ok +test_updateAETy (__main__.TestAE) ... ok +test_updateAEPi (__main__.TestAE) ... ok +test_updateAEUnknownAttribute (__main__.TestAE) ... ok +test_retrieveAEACP (__main__.TestAE) ... ok +test_deleteAEByUnknownOriginator (__main__.TestAE) ... ok +test_deleteAEByAssignedOriginator (__main__.TestAE) ... ok +---------------------------------------------------------------------- +Ran 12 tests in 0.116s +OK +``` The individual test suites are located in the [tests](https://github.com/ankraft/ACME-oneM2M-CSE/tree/master/tests){target=_new} directory. Their names start with *test...* and are grouped by the aspect of the CSE they are testing. @@ -59,47 +58,46 @@ The individual test suites are located in the [tests](https://github.com/ankraft The `--help` or `-h` command line argument provides a usage overview for the *runTest.py* script. -=== "Test Runner Overview" - ```text - $ python runTests.py -h - usage: runTests.py [-h] [--all] [--load-only] [--verbose-requests] - [--disable-teardown] [--run-teardown] - [--run-count NUMBEROFRUNS] - [--run-tests TESTCASENAME [TESTCASENAME ...]] - [--show-skipped] [--no-failfast] - [--list-tests | --list-tests-sorted] - [TESTSUITE ...] - - positional arguments: - TESTSUITE specific test suites to run. Run all test suites - if empty - - options: - -h, --help show this help message and exit - --all run all test suites (including load tests) - --load-only run only load test suites - --verbose-requests, -v - show verbose requests, responses and - notifications output - --disable-teardown, -notd - disable the tear-down / cleanup procedure at the - end of a test suite - --run-teardown, -runtd - run the specified test cases' tear-down - functions and exit - --run-count NUMBEROFRUNS - run each test suite n times (default: 1) - --run-tests TESTCASENAME [TESTCASENAME ...], -run TESTCASENAME [TESTCASENAME ...] - run only the specified test cases from the set - of test suites - --show-skipped show skipped test cases in summary - --no-failfast continue running test cases after a failure - --list-tests, -ls list the test cases of the specified test suites - in the order they are defined and exit - --list-tests-sorted, -lss - alphabetical sorted list the test cases of the - specified test suites and exit - ``` +```text title="Test Runner Overview" +$ python runTests.py -h +usage: runTests.py [-h] [--all] [--load-only] [--verbose-requests] + [--disable-teardown] [--run-teardown] + [--run-count NUMBEROFRUNS] + [--run-tests TESTCASENAME [TESTCASENAME ...]] + [--show-skipped] [--no-failfast] + [--list-tests | --list-tests-sorted] + [TESTSUITE ...] + +positional arguments: +TESTSUITE specific test suites to run. Run all test suites + if empty + +options: +-h, --help show this help message and exit +--all run all test suites (including load tests) +--load-only run only load test suites +--verbose-requests, -v + show verbose requests, responses and + notifications output +--disable-teardown, -notd + disable the tear-down / cleanup procedure at the + end of a test suite +--run-teardown, -runtd + run the specified test cases' tear-down + functions and exit +--run-count NUMBEROFRUNS + run each test suite n times (default: 1) +--run-tests TESTCASENAME [TESTCASENAME ...], -run TESTCASENAME [TESTCASENAME ...] + run only the specified test cases from the set + of test suites +--show-skipped show skipped test cases in summary +--no-failfast continue running test cases after a failure +--list-tests, -ls list the test cases of the specified test suites + in the order they are defined and exit +--list-tests-sorted, -lss + alphabetical sorted list the test cases of the + specified test suites and exit +``` ### Running the Tests @@ -107,52 +105,51 @@ The Python script [runTests.py](https://github.com/ankraft/ACME-oneM2M-CSE/blob/ Usually, the test suites are run only once, but one can specify the *--run-count* option to execute tests multiple times. -=== "Example Test Run" - ```text - $ python3 runTests.py - - ... - - [ACME] - Test Results - ┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ ┃ ┃ ┃ ┃ Times ┃ Exec Time per ┃ Proc Time per ┃ ┃ - ┃ Test Suite ┃ Count ┃ Skipped ┃ Errors ┃ Exec | Sleep | Proc ┃ Test | Request ┃ Test | Request ┃ Requests ┃ - ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩ - │ testACP │ 42 │ 0 │ 0 │ 0.5873 | 0.00 | 0.1181 │ 0.0140 | 0.0109 │ 0.0028 | 0.0022 │ 54 │ - │ testACTR │ 3 │ 0 │ 0 │ 0.0478 | 0.00 | 0.0127 │ 0.0159 | 0.0080 │ 0.0042 | 0.0021 │ 6 │ - │ testAE │ 25 │ 0 │ 0 │ 0.4417 | 0.00 | 0.0735 │ 0.0177 | 0.0134 │ 0.0029 | 0.0022 │ 33 │ - │ testAddressing │ 8 │ 0 │ 0 │ 0.1895 | 0.00 | 0.0308 │ 0.0237 | 0.0135 │ 0.0039 | 0.0022 │ 14 │ - │ testCIN │ 17 │ 0 │ 0 │ 0.3655 | 0.00 | 0.0585 │ 0.0215 | 0.0131 │ 0.0034 | 0.0021 │ 28 │ - │ testCNT │ 21 │ 0 │ 0 │ 0.4119 | 0.00 | 0.0706 │ 0.0196 | 0.0158 │ 0.0034 | 0.0027 │ 26 │ - │ testCNT_CIN │ 26 │ 0 │ 0 │ 4.9397 | 4.00 | 0.2228 │ 0.1900 | 0.0610 │ 0.0086 | 0.0028 │ 81 │ - │ testCRS │ 62 │ 0 │ 0 │ 84.0746 | 81.80 | 0.5280 │ 1.3560 | 0.6369 │ 0.0085 | 0.0040 │ 132 │ - │ testCSE │ 8 │ 0 │ 0 │ 0.1862 | 0.00 | 0.0503 │ 0.0233 | 0.0233 │ 0.0063 | 0.0063 │ 8 │ - │ testDiscovery │ 54 │ 0 │ 0 │ 1.0766 | 0.00 | 0.1789 │ 0.0199 | 0.0135 │ 0.0033 | 0.0022 │ 80 │ - │ testExpiration │ 8 │ 0 │ 0 │ 30.7233 | 30.00 | 0.1932 │ 3.8404 | 0.7145 │ 0.0242 | 0.0045 │ 43 │ - │ testFCNT │ 23 │ 0 │ 0 │ 0.4121 | 0.00 | 0.0804 │ 0.0179 | 0.0153 │ 0.0035 | 0.0030 │ 27 │ - │ testFCNT_FCI │ 11 │ 0 │ 0 │ 0.2775 | 0.00 | 0.0425 │ 0.0252 | 0.0116 │ 0.0039 | 0.0018 │ 24 │ - │ testGRP │ 28 │ 0 │ 0 │ 0.7579 | 0.00 | 0.1099 │ 0.0271 | 0.0138 │ 0.0039 | 0.0020 │ 55 │ - │ testMgmtObj │ 80 │ 0 │ 0 │ 1.0088 | 0.00 | 0.1998 │ 0.0126 | 0.0123 │ 0.0025 | 0.0024 │ 82 │ - │ testMisc │ 18 │ 0 │ 0 │ 4.4119 | 4.00 | 0.1615 │ 0.2451 | 0.1765 │ 0.0090 | 0.0065 │ 25 │ - │ testNOD │ 12 │ 0 │ 0 │ 0.3239 | 0.00 | 0.0770 │ 0.0270 | 0.0130 │ 0.0064 | 0.0031 │ 25 │ - │ testPCH │ 13 │ 0 │ 0 │ 0.1699 | 0.00 | 0.0395 │ 0.0131 | 0.0089 │ 0.0030 | 0.0021 │ 19 │ - │ testPCH_PCU │ 11 │ 0 │ 0 │ 30.8078 | 15.00 | 0.3497 │ 2.8007 | 0.7514 │ 0.0318 | 0.0085 │ 41 │ - │ testREQ │ 25 │ 0 │ 0 │ 46.5237 | 45.00 | 0.5464 │ 1.8609 | 1.1347 │ 0.0219 | 0.0133 │ 41 │ - │ testRemote │ 7 │ 0 │ 0 │ 0.2121 | 0.00 | 0.0533 │ 0.0303 | 0.0151 │ 0.0076 | 0.0038 │ 14 │ - │ testRemote_Annc │ 37 │ 0 │ 0 │ 0.7482 | 0.00 | 0.1244 │ 0.0202 | 0.0125 │ 0.0034 | 0.0021 │ 60 │ - │ testRemote_GRP │ 2 │ 0 │ 0 │ 0.0659 | 0.00 | 0.0113 │ 0.0330 | 0.0132 │ 0.0056 | 0.0023 │ 5 │ - │ testRemote_Requests │ 2 │ 0 │ 0 │ 0.0677 | 0.00 | 0.0119 │ 0.0339 | 0.0135 │ 0.0060 | 0.0024 │ 5 │ - │ testRequests │ 12 │ 0 │ 0 │ 10.3716 | 0.00 | 0.1257 │ 0.8643 | 0.7408 │ 0.0105 | 0.0090 │ 14 │ - │ testSMD │ 14 │ 0 │ 0 │ 0.2395 | 0.00 | 0.0587 │ 0.0171 | 0.0133 │ 0.0042 | 0.0033 │ 18 │ - │ testSUB │ 81 │ 0 │ 0 │ 16.9685 | 15.00 | 0.4738 │ 0.2095 | 0.1266 │ 0.0058 | 0.0035 │ 134 │ - │ testTS │ 33 │ 0 │ 0 │ 0.5149 | 0.00 | 0.1064 │ 0.0156 | 0.0135 │ 0.0032 | 0.0028 │ 38 │ - │ testTSB │ 7 │ 0 │ 0 │ 6.2509 | 6.00 | 0.1507 │ 0.8930 | 0.4808 │ 0.0215 | 0.0116 │ 13 │ - │ testTS_TSI │ 29 │ 0 │ 0 │ 121.4479 | 119.41 | 0.5758 │ 4.1879 | 1.1142 │ 0.0199 | 0.0053 │ 109 │ - │ testUpperTester │ 6 │ 0 │ 0 │ 0.3926 | 0.00 | 0.0318 │ 0.0654 | 0.1963 │ 0.0053 | 0.0159 │ 2 │ - ├─────────────────────┼───────┼─────────┼────────┼──────────────────────────────┼───────────────────┼───────────────────┼──────────┤ - │ Totals │ 725 │ 0 │ 0 │ 365.0405 | 320.21 | 4.8910 │ 0.5035 | 0.2906 │ 0.0067 | 0.0039 │ 1256 │ - └─────────────────────┴───────┴─────────┴────────┴──────────────────────────────┴───────────────────┴───────────────────┴──────────┘ - ``` +```text title="Example Test Run" +$ python3 runTests.py + +... + + [ACME] - Test Results +┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓ +┃ ┃ ┃ ┃ ┃ Times ┃ Exec Time per ┃ Proc Time per ┃ ┃ +┃ Test Suite ┃ Count ┃ Skipped ┃ Errors ┃ Exec | Sleep | Proc ┃ Test | Request ┃ Test | Request ┃ Requests ┃ +┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩ +│ testACP │ 42 │ 0 │ 0 │ 0.5873 | 0.00 | 0.1181 │ 0.0140 | 0.0109 │ 0.0028 | 0.0022 │ 54 │ +│ testACTR │ 3 │ 0 │ 0 │ 0.0478 | 0.00 | 0.0127 │ 0.0159 | 0.0080 │ 0.0042 | 0.0021 │ 6 │ +│ testAE │ 25 │ 0 │ 0 │ 0.4417 | 0.00 | 0.0735 │ 0.0177 | 0.0134 │ 0.0029 | 0.0022 │ 33 │ +│ testAddressing │ 8 │ 0 │ 0 │ 0.1895 | 0.00 | 0.0308 │ 0.0237 | 0.0135 │ 0.0039 | 0.0022 │ 14 │ +│ testCIN │ 17 │ 0 │ 0 │ 0.3655 | 0.00 | 0.0585 │ 0.0215 | 0.0131 │ 0.0034 | 0.0021 │ 28 │ +│ testCNT │ 21 │ 0 │ 0 │ 0.4119 | 0.00 | 0.0706 │ 0.0196 | 0.0158 │ 0.0034 | 0.0027 │ 26 │ +│ testCNT_CIN │ 26 │ 0 │ 0 │ 4.9397 | 4.00 | 0.2228 │ 0.1900 | 0.0610 │ 0.0086 | 0.0028 │ 81 │ +│ testCRS │ 62 │ 0 │ 0 │ 84.0746 | 81.80 | 0.5280 │ 1.3560 | 0.6369 │ 0.0085 | 0.0040 │ 132 │ +│ testCSE │ 8 │ 0 │ 0 │ 0.1862 | 0.00 | 0.0503 │ 0.0233 | 0.0233 │ 0.0063 | 0.0063 │ 8 │ +│ testDiscovery │ 54 │ 0 │ 0 │ 1.0766 | 0.00 | 0.1789 │ 0.0199 | 0.0135 │ 0.0033 | 0.0022 │ 80 │ +│ testExpiration │ 8 │ 0 │ 0 │ 30.7233 | 30.00 | 0.1932 │ 3.8404 | 0.7145 │ 0.0242 | 0.0045 │ 43 │ +│ testFCNT │ 23 │ 0 │ 0 │ 0.4121 | 0.00 | 0.0804 │ 0.0179 | 0.0153 │ 0.0035 | 0.0030 │ 27 │ +│ testFCNT_FCI │ 11 │ 0 │ 0 │ 0.2775 | 0.00 | 0.0425 │ 0.0252 | 0.0116 │ 0.0039 | 0.0018 │ 24 │ +│ testGRP │ 28 │ 0 │ 0 │ 0.7579 | 0.00 | 0.1099 │ 0.0271 | 0.0138 │ 0.0039 | 0.0020 │ 55 │ +│ testMgmtObj │ 80 │ 0 │ 0 │ 1.0088 | 0.00 | 0.1998 │ 0.0126 | 0.0123 │ 0.0025 | 0.0024 │ 82 │ +│ testMisc │ 18 │ 0 │ 0 │ 4.4119 | 4.00 | 0.1615 │ 0.2451 | 0.1765 │ 0.0090 | 0.0065 │ 25 │ +│ testNOD │ 12 │ 0 │ 0 │ 0.3239 | 0.00 | 0.0770 │ 0.0270 | 0.0130 │ 0.0064 | 0.0031 │ 25 │ +│ testPCH │ 13 │ 0 │ 0 │ 0.1699 | 0.00 | 0.0395 │ 0.0131 | 0.0089 │ 0.0030 | 0.0021 │ 19 │ +│ testPCH_PCU │ 11 │ 0 │ 0 │ 30.8078 | 15.00 | 0.3497 │ 2.8007 | 0.7514 │ 0.0318 | 0.0085 │ 41 │ +│ testREQ │ 25 │ 0 │ 0 │ 46.5237 | 45.00 | 0.5464 │ 1.8609 | 1.1347 │ 0.0219 | 0.0133 │ 41 │ +│ testRemote │ 7 │ 0 │ 0 │ 0.2121 | 0.00 | 0.0533 │ 0.0303 | 0.0151 │ 0.0076 | 0.0038 │ 14 │ +│ testRemote_Annc │ 37 │ 0 │ 0 │ 0.7482 | 0.00 | 0.1244 │ 0.0202 | 0.0125 │ 0.0034 | 0.0021 │ 60 │ +│ testRemote_GRP │ 2 │ 0 │ 0 │ 0.0659 | 0.00 | 0.0113 │ 0.0330 | 0.0132 │ 0.0056 | 0.0023 │ 5 │ +│ testRemote_Requests │ 2 │ 0 │ 0 │ 0.0677 | 0.00 | 0.0119 │ 0.0339 | 0.0135 │ 0.0060 | 0.0024 │ 5 │ +│ testRequests │ 12 │ 0 │ 0 │ 10.3716 | 0.00 | 0.1257 │ 0.8643 | 0.7408 │ 0.0105 | 0.0090 │ 14 │ +│ testSMD │ 14 │ 0 │ 0 │ 0.2395 | 0.00 | 0.0587 │ 0.0171 | 0.0133 │ 0.0042 | 0.0033 │ 18 │ +│ testSUB │ 81 │ 0 │ 0 │ 16.9685 | 15.00 | 0.4738 │ 0.2095 | 0.1266 │ 0.0058 | 0.0035 │ 134 │ +│ testTS │ 33 │ 0 │ 0 │ 0.5149 | 0.00 | 0.1064 │ 0.0156 | 0.0135 │ 0.0032 | 0.0028 │ 38 │ +│ testTSB │ 7 │ 0 │ 0 │ 6.2509 | 6.00 | 0.1507 │ 0.8930 | 0.4808 │ 0.0215 | 0.0116 │ 13 │ +│ testTS_TSI │ 29 │ 0 │ 0 │ 121.4479 | 119.41 | 0.5758 │ 4.1879 | 1.1142 │ 0.0199 | 0.0053 │ 109 │ +│ testUpperTester │ 6 │ 0 │ 0 │ 0.3926 | 0.00 | 0.0318 │ 0.0654 | 0.1963 │ 0.0053 | 0.0159 │ 2 │ +├─────────────────────┼───────┼─────────┼────────┼──────────────────────────────┼───────────────────┼───────────────────┼──────────┤ +│ Totals │ 725 │ 0 │ 0 │ 365.0405 | 320.21 | 4.8910 │ 0.5035 | 0.2906 │ 0.0067 | 0.0039 │ 1256 │ +└─────────────────────┴───────┴─────────┴────────┴──────────────────────────────┴───────────────────┴───────────────────┴──────────┘ +``` With `--verbose-requests` the each request and response is printed as well. This can be helpful to debug problems with the system under test, the network, and other aspects. @@ -160,10 +157,9 @@ With `--verbose-requests` the each request and response is printed as well. This One can specify which test suites to run like this: -=== "Example Test Suite Run" - ```bash - $ python3 runTests.py testACP testCin - ``` +```bash title="Run Specific Test Suites" +$ python3 runTests.py testACP testCin +``` The *runTest.py* script by default will run all test suites, **except** scripts that run load tests. To include those one need to specify the `--load-include` command line argument. @@ -172,10 +168,9 @@ The *runTest.py* script by default will run all test suites, **except** scripts It is also possible to run individual test cases from test suites. This is done by optionally specify the test suites and then with the `--run-tests` option a list of test case names to run: -=== "Example Single Test Case Run" - ```bash - $ python runTests.py testSUB --run-tests test_createCNTforEXC - ``` +```bash title="Run Single Test Case" +$ python runTests.py testSUB --run-tests test_createCNTforEXC +``` If test cases appear more than once one can specify the order in which the test cases are run. Example: @@ -189,12 +184,11 @@ If test cases appear more than once one can specify the order in which the test The most interesting use of this functionionality is to run a whole test suite together with the `--disable-teardown` option up to the point of a failure, and then run the failed test case again: -=== "Example Single Test Case Run Without Tear-Down" - ```bash - $ python runTests.py testSUB --disable-teardown - ... - $ python runTests.py testSUB --run-tests test_createCNTforEXC - ``` +```bash title="Run Single Test Case Without Tear-Down" +$ python runTests.py testSUB --disable-teardown +... +$ python runTests.py testSUB --run-tests test_createCNTforEXC +``` This disables the clean-up of the CSE after the test suite has run, so that the resources created by the test suite are still present in the CSE. This way one can investigate the state of the CSE after the test suite has run. diff --git a/docs/docs/help/Contributing.md b/docs/docs/help/Contributing.md index d60fd077..04d5614f 100644 --- a/docs/docs/help/Contributing.md +++ b/docs/docs/help/Contributing.md @@ -5,24 +5,11 @@ extensions: Contributions to ACME are welcome! Here's how to get started: 1. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. -2. Fork the repository [https://github.com/ankraft/ACME-oneM2M-CSE](https://github.com/ankraft/ACME-oneM2M-CSE){target=_new} on Github, +1. Fork the repository [https://github.com/ankraft/ACME-oneM2M-CSE](https://github.com/ankraft/ACME-oneM2M-CSE){target=_new} on Github, create a new branch off the *master* branch and start making your changes (known as [GitHub Flow](https://guides.github.com/introduction/flow/index.html){target=_new}). -3. Write a test which shows that the bug was fixed or that the feature works +1. ACME makes use of [typing hints](../development/TypeChecking.md) and is statically type-checked with [mypy](https://mypy-lang.org){target=_new}. Please make sure that your changes pass the type checking. +1. Write a test which shows that the bug was fixed or that the feature works as expected. -4. Send a pull request and bug the maintainers until it gets merged and +1. Send a pull request and bug the maintainers until it gets merged and published. - - -## Acknowledgements - -Thank you for contributed ideas, code, patches, testing, bug fixes, time, and more! - -![reinaortega](https://github.com/reinaortega.png?size=24) [Miguel Angel Reina Ortega](https://github.com/reinaortega){target=_new} -![BobFIV](https://github.com/BobFIV.png?size=24) [BobFIV](https://github.com/BobFIV){target=_new} - [Ken Figueredo](https://www.linkedin.com/in/kenfigueredo/){target=_new} -![YannGarcia](https://github.com/YannGarcia.png?size=24) [Yann Garcia](https://github.com/YannGarcia){target=_new} - [Massimo Vanetti](https://github.com/massimov){target=_new} - [Tyler Sengia](https://www.linkedin.com/in/tyler-sengia/){target=_new} -![JiriD85](https://github.com/JiriD85.png?size=24) [JiriD85](https://github.com/JiriD85){target=_new} -![samuelbles07](https://github.com/samuelbles07.png?size=24) [samuelbles07](https://github.com/samuelbles07){target=_new} diff --git a/docs/docs/help/FAQ.md b/docs/docs/help/FAQ.md index a7083e77..395ec957 100644 --- a/docs/docs/help/FAQ.md +++ b/docs/docs/help/FAQ.md @@ -78,14 +78,14 @@
Example for an IN-CSE with the CSE-ID "*/id-in*": - ```ini + ```ini title="Example to allow a CSE with the CSE-ID 'id-mn' to register to the IN-CSE" [cse.registration] allowedCSROriginators=id-mn ```
And for an MN-CSE with the CSE-ID "*/id-mn*": - ```ini + ```ini title="Example to allow the IN-CSE with the CSE-ID 'id-in' to get access" [cse.registration] allowedCSROriginators=id-in ``` @@ -102,7 +102,7 @@ When running the CSE with the database mode set to *disk* (ie. store the database on disk rather then in memory) one can improve the performance by increasing the time before data is actually written to disk. The default is 1 second, but it can be increased as necessary. Be aware, though, that the risk of losing data increases with higher delays in case of a crash or when the CSE shutdown is interrupted. - ```ini + ```ini title="Example to set the write delay to 10 seconds" [database] writeDelay=10 ``` @@ -122,7 +122,7 @@ 1. **There is an error message "UnicodeEncodeError: 'latin-1' codec can't encode character"** This error message is shown when the console tries to display a character that is not supported by the current console encoding. Try to set the console encoding to UTF-8 by setting the environment variable *PYTHONIOENCODING* to *utf-8*, for example: - ```bash + ```bash title="Set the terminal console encoding to UTF-8" export PYTHONIOENCODING=utf-8 ``` @@ -135,7 +135,7 @@ 1. **The console or the text UI is not displayed correctly** It could be that the OS's terminal applications doesn't support rendering of extra characters, like line graphics. One recommendation on Linux systems is to install the [Mate Terminal](https://wiki.mate-desktop.org/mate-desktop/applications/mate-terminal/){target=_new}, which supports UTF-8 and line graphics. It also renders the output much faster. - ```bash + ```bash title="Install the Mate Terminal" sudo apt-get install mate-terminal ``` diff --git a/docs/docs/home/Acknowledgements.md b/docs/docs/home/Acknowledgements.md new file mode 100644 index 00000000..6ade8c79 --- /dev/null +++ b/docs/docs/home/Acknowledgements.md @@ -0,0 +1,13 @@ +# Acknowledgements + +TODO +Thank you for contributed ideas, code, patches, testing, bug fixes, time, and more! + +![reinaortega](https://github.com/reinaortega.png?size=24) [Miguel Angel Reina Ortega](https://github.com/reinaortega){target=_new} +![BobFIV](https://github.com/BobFIV.png?size=24) [BobFIV](https://github.com/BobFIV){target=_new} + [Ken Figueredo](https://www.linkedin.com/in/kenfigueredo/){target=_new} +![YannGarcia](https://github.com/YannGarcia.png?size=24) [Yann Garcia](https://github.com/YannGarcia){target=_new} + [Massimo Vanetti](https://github.com/massimov){target=_new} + [Tyler Sengia](https://www.linkedin.com/in/tyler-sengia/){target=_new} +![JiriD85](https://github.com/JiriD85.png?size=24) [JiriD85](https://github.com/JiriD85){target=_new} +![samuelbles07](https://github.com/samuelbles07.png?size=24) [samuelbles07](https://github.com/samuelbles07){target=_new} diff --git a/docs/docs/home/License.md b/docs/docs/home/License.md index c6bbbb2a..cf1ed54b 100644 --- a/docs/docs/home/License.md +++ b/docs/docs/home/License.md @@ -3,7 +3,6 @@ The ACME oneM2M CSE is available under the BSD 3-Clause License for the CSE and its native components and modules. Please see the individual licenses of the used third-party components. - ## BSD 3-Clause License Copyright (c) 2020, Andreas Kraft diff --git a/docs/docs/home/Supported.md b/docs/docs/home/Supported.md index acb9518f..a9fa7014 100644 --- a/docs/docs/home/Supported.md +++ b/docs/docs/home/Supported.md @@ -1,12 +1,14 @@ # Supported Resource Types and Functionalities -## oneM2M Specification Conformance - -The CSE implementation successfully passes all the relevant oneM2M test cases for the supported resource types and features. +This article lists the supported oneM2M resource types, functionalities, and additional runtime features of the ACME CSE. ## oneM2M Features +### oneM2M Specification Conformance + +The CSE implementation successfully passes all the relevant oneM2M test cases for the supported resource types, attributes and behaviours. + ### Release Versions The ACME CSE supports oneM2M release 1 - 4 and the upcoming release 5 for the supported resource types and functionalities listed below. @@ -106,12 +108,12 @@ The following serialization types are supported: | CBOR | ✓ | | | XML | ✗ | | -The supported serializations can be used together, e.g. between different or even the same entity. +The supported serializations can be used together, e.g. between different or even with the same entity. ### Result Content Types -The following result contents are implemented for standard oneM2M requests & discovery: +The following result contents are implemented for standard oneM2M requests and discovery: | Discovery Type | RCN | |:---------------------------------------|:---:| diff --git a/docs/docs/howtos/Docker.md b/docs/docs/howtos/Docker.md index 26fbbafc..e33a8c8b 100644 --- a/docs/docs/howtos/Docker.md +++ b/docs/docs/howtos/Docker.md @@ -9,18 +9,15 @@ A Docker image with reasonable defaults is available on Docker Hub: [https://hub You can download and run it with the following shell command: -=== "Download and Run" - ```sh - docker run -it -p 8080:8080 --rm --name acme-onem2m-cse ankraft/acme-onem2m-cse - ``` - +```sh title="Download Image and Run" +docker run -it -p 8080:8080 --rm --name acme-onem2m-cse ankraft/acme-onem2m-cse +``` To adjust the output to the current terminal width run the image with the following command: -=== "Run with Terminal Width" - ```sh - docker run -e COLUMNS="`tput cols`" -e LINES="`tput lines`" -it -p 8080:8080 --rm --name acme-onem2m-cse ankraft/acme-onem2m-cse - ``` +```sh title="Run Container with Terminal Width" +docker run -e COLUMNS="`tput cols`" -e LINES="`tput lines`" -it -p 8080:8080 --rm --name acme-onem2m-cse ankraft/acme-onem2m-cse +``` ## Build Your Own Docker Image @@ -36,10 +33,9 @@ The build script takes all the current scripts, attribute definitions etc. from The Docker image uses the *data* directory as the base directory for the CSE's runtime data. This directory can be mapped to a volume on the host system. For example, to use a local data directory as the base directory, run the following command: -=== "Run with Mapped Base Directory" - ```sh - docker run -it -p 8080:8080 -v /path/to/data:/data --rm --name acme-onem2m-cse ankraft/acme-onem2m-cse - ``` +```sh title="Run Container with Mapped Base Directory" +docker run -it -p 8080:8080 -v /path/to/data:/data --rm --name acme-onem2m-cse ankraft/acme-onem2m-cse +``` This is useful for persisting data across container restarts and to provide a different configuration file that is then used instead of the default *acme.ini* file. @@ -52,20 +48,18 @@ One example is to provide the Docker host's IP address to the CSE as the *cseHos The setting for *cseHost* in the *acme.ini* file should should be changed to the following: -=== "Environment Variable to set Host IP" - ```ini - [basic.config] - ... - cseHost=${DOCKER_HOST_IP} - ... - ``` +```ini title="Use Environment Variable to set Host IP" +[basic.config] +... +cseHost=${DOCKER_HOST_IP} +... +``` The value for this setting can be provided by setting the environment variable *DOCKER_HOST_IP* to the Docker host's IP address: -=== "Run with Docker Host IP Environment Variable" - ```sh - docker run -it -p 8080:8080 -v /path/to/data:/data -e DOCKER_HOST_IP=`ifconfig en0 | awk '$1 == "inet" {print $2}'` -rm --name acme-onem2m-cse ankraft/acme-onem2m-cse - ``` +```sh title="Run Container with Docker Host IP Environment Variable" +docker run -it -p 8080:8080 -v /path/to/data:/data -e DOCKER_HOST_IP=`ifconfig en0 | awk '$1 == "inet" {print $2}'` -rm --name acme-onem2m-cse ankraft/acme-onem2m-cse +``` Values for other setting, such as credentials, can be provided the same way. diff --git a/docs/docs/howtos/ExperimentalWebSocketBinding.md b/docs/docs/howtos/ExperimentalWebSocketBinding.md new file mode 100644 index 00000000..345a1f61 --- /dev/null +++ b/docs/docs/howtos/ExperimentalWebSocketBinding.md @@ -0,0 +1,39 @@ +# Experimental WebSocket Binding + +In release 2024.03 the ACME CSE got support for the WebSocket protocol. This protocol offers an always-on connection between a client and a server for fast data transfer. The performance gain comes mostly from the fact that with WebSockets it is not necessary to establish new network connections, opening sockets etc. every time when a request is sent. + +This binding is especially useful for CSE-to-CSE connections when two CSEs are constantly exchanging requests and responses. However, it is not really suited for devices (ADN) to use this protocol binding because the drawback is that computing and network resources are kept constantly assigned by both parties. It may make sense, though, to support this binding on an ADN whenever the use case is bulk and high frequency transfer of requests, such in the case of time series data. + +## WebSocket and Originators + +The technical specification is published in oneM2M's TS-0020 and available [on the specification page](https://onem2m.org/technical/published-specifications) . Unfortunately, there is a small issue in the specification when it comes to send notifications from, for example, a CSE to a connected AE or CSE. + +The specification states that an established WebSocket connection must be used when sending notifications to a client (an AE or CSE). In normal cases, this is not a problem. However, there could be a situation that a client establishes a WebSocket connection but doesn't send a request immediately. This is normal behavior, but without a oneM2M request the CSE cannot associate that specific WebSocket connection with an originator. If in this case the CSE needs to send a notification to a client (ie. an originator) it does not know whether there is an established WebSocket connection, even if, technically, there is one. + +The solution for this is that a client needs to add an additional header when opening a WebSocket connection. This is similar to the `X-M2M-Origin` header in the oneM2M HTTP binding, and in fact the experimental feature proposes that this header must be present when a WebSocket connection is opened. + +```python title="Example Python Code" +websocket = connect(cseUrl, + subprotocols=['oneM2M.json'], + additional_headers={ 'X-M2M-Origin': anOriginator }) +``` + +The only exception is when registering an AE. In this case, again similar to the HTTP binding, this header may be absent. + +## Establishing WebSocket Connections + +Another experimental feature is that WebSockets may be established from AEs as well as CSE's (or *registrees* and *registrars* in oneM2M terms). The original (current) specification states that WebSockets must only be established by a *registree*, but this is very limiting and may force small ADN devices to implement multiple oneM2M bindings technologies when they want to be able to receive notifications even when no WebSocket connection has been established. Also, it is not clearly specified how to store requests from a *registrar* to a *registree* in case a connection is not available at the moment. + +The implemented experimental feature now adds the following procedure and a special URL schema for the *poa* (point of access) attribute for WebSocket connections: + +- If there is an established WebSocket connection for a request originator then send the request over this connection. +- If there is no established WebSocket connection: + 1. If there is a URL in the *poa* attribute with the value `ws://default` then don't open a new WebSocket connection (because only the default one should be used). Continue with step iii. + 2. If there is a "normal" WebSocket URL then establish a WebSocket connection to that URL and send the request and await the response. Afterwards the connection may be closed. + 3. Otherwise follow the usual procedure for *poa* handling, ie. look for other means to reach the originator. + +To support this feature, of course, a *registree* must implement a WebSocket server as well. + +## Changes to oneM2M's TS-0020 WebSocket Binding Specification + +These changes were submitted as a Change Request *SDS-2024-0021* to TS-0020 (March 2024) and will be discussed in oneM2M's SDS working group. \ No newline at end of file diff --git a/docs/docs/howtos/ExportResources.md b/docs/docs/howtos/ExportResources.md new file mode 100644 index 00000000..f6551c8b --- /dev/null +++ b/docs/docs/howtos/ExportResources.md @@ -0,0 +1,75 @@ +# How to Export Resources + +Sometimes it is necessary to export resource, for example to backup a part of the resource tree or to save the state of the resource tree for demonstration and experiments. + +## Export Resources + +The ACME CSE offers a simple and portable way to export single resources or a whole part of the resource tree. In the text UI when clicking on a resource a tab *Services* contains the service "Export Resource". When clicking on the "Export" button a resource and (depending on the "child resource" checkbox) its child resources are exported to a directory as a shell script with the current date and time. The directory is the *tmp* directory under the CSE's root directory. + +
+![Text UI - Export Resources](../images/export_resource.png) +
Text UI - Export Resources
+
+ +The generated script contains the necessary commands to send Mca CREATE requests using *curl* commands over http for the exported resources. + +One is free to make modifications to the exported resources as necessary, or to combine various resource scripts into a single script. + +## Import Resources Again + +The generated shell script contains three sections: + +- The variable `cseURL` that is set to the URL of a CSE where the resources will be imported again. This should be set to appropriate address when targeting another CSE. +- Shell functions that construct the CREATE requests and send it using the *curl* command line tool. +- At the bottom are the shell function calls with the originator, resource type, and resource representations. + +!!! Import + The resource representations can only contain the resource attributes that can be present in CREATE requests. This means, for example, that the *resourceID* of a resource is not present. + This also means, unfortunately, that references between resource may be incomplete after an export and need to be set manually afterwards. + +To import the resources in an export script just run the script in a (bash) shell: + +```sh title="Run the export script" +$ sh export-20240316T131612.sh +``` + +## Example Script + +The following is an example of an export script that exports a container with two content instances and a subscription: + +```sh title="Example export script" +#!/bin/bash +# Exported cnt6834189228603991262 from id-in at 20240316T131612,894875 + +cseURL=http://localhost:8080 # (1)! + +function uniqueNumber() { # (2)! + unique_number="" + for i in {1..10} + do + unique_number+=$RANDOM + done + unique_number=${unique_number:0:10} + echo "$unique_number" +} + +function createResource() { # (3)! + printf '\nCreating child resource under %s\n' $cseURL/$4 + printf 'Result: ' + curl -X POST -H "X-M2M-Origin: $1" -H "X-M2M-RVI: 4" -H "X-M2M-RI: $(uniqueNumber)" -H "Content-Type: application/json;ty=$2" -d "$3" $cseURL/$4 + printf '\n' +} + +# (4)! + +createResource CDemoLightswitch 3 '{"m2m:cnt": {"rn": "switchContainer", "mni": 10, "acpi": ["acp3542208976028337519"]}}' 'cse-in/CDemoLightswitch' +createResource CDemoLightswitch 23 '{"m2m:sub": {"rn": "switchSubscription", "nu": ["cse-in/CDemoLightbulb"], "enc": {"net": [3]}, "nct": 1}}' 'cse-in/CDemoLightswitch/switchContainer' +createResource CDemoLightswitch 4 '{"m2m:cin": {"con": "off", "rn": "cin_KJyrTD7INf"}}' 'cse-in/CDemoLightswitch/switchContainer' +createResource CDemoLightswitch 4 '{"m2m:cin": {"con": "off", "rn": "cin_MQ5AK9WRbs"}}' 'cse-in/CDemoLightswitch/switchContainer' + +``` + +1. The variable `cseURL` is set to the URL of the CSE where the resources will be imported again. This should be set to the appropriate address when targeting another CSE. +2. This function generates a unique number that is used for various identifiers. +3. This function creates a resource in the CSE using the *curl* command line tool. The function takes four arguments: the originator, the resource type, the resource representation, and the parent resource's URL. +4. From here on the script creates the resources. The `createResource` function is called with the originator, the resource type, the resource representation, and the parent resource's URL. diff --git a/docs/docs/howtos/HowTo-pyenv.md b/docs/docs/howtos/HowTo-pyenv.md index 8456ae14..bce6d9c7 100644 --- a/docs/docs/howtos/HowTo-pyenv.md +++ b/docs/docs/howtos/HowTo-pyenv.md @@ -16,35 +16,31 @@ This guide assumes that you have installed the [homebrew](https://brew.sh){targe If not done yet, we need to install the *Xcode* dependencies: -=== "Install Xcode dependencies" - ```sh - Xcode-select --install - ``` +```sh title="Install Xcode dependencies" +Xcode-select --install +``` ## Installing pyenv Install *pyenv* using *homebrew*: -=== "Install pyenv" - ```sh - brew upgrade - brew install pyenv - ``` +```sh title="Install pyenv" +brew upgrade +brew install pyenv +``` Install extra libraries: -=== "Install extra libraries" - ```sh - brew install readline xz - ``` +```sh title="Install extra libraries" +brew install readline xz +``` Install *virtualenv* using *homebrew*: -=== "Install pyenv-virtualenv" - ```sh - brew install pyenv-virtualenv - ``` +```sh title="Install pyenv-virtualenv" +brew install pyenv-virtualenv +``` ## Getting Started @@ -53,22 +49,19 @@ We can now use *pyenv* and *pyenv-virtualenv*. We start with installing a Python The following command installs *Python 3.11.7* in *pyenv*: -=== "Install a Python version" - ```sh - pyenv install 3.11.7 - ``` +```sh title="Install a Python Version" +pyenv install 3.11.7 +``` Since we want to keep the installed base Python version "clean2, we will create a new virtual environment, e.g. taking version *3.11.7* as a base version. The following command will create a virtual environment *acme-3.11* that can be used later on: -=== "Create a virtual environment" - ```sh - pyenv virtualenv 3.11.7 acme-3.11 - ``` +```sh title="Create a Virtual Environment" +pyenv virtualenv 3.11.7 acme-3.11 +``` We can now enable a virtual environment for the local directory: -=== "Enable a virtual environment for the local directory" - ```sh - pyenv local acme-3.11 - ``` +```sh title="Enable a virtual environment for the local directory" +pyenv local acme-3.11 +``` diff --git a/docs/docs/howtos/HowTos.md b/docs/docs/howtos/HowTos.md new file mode 100644 index 00000000..baaa91a3 --- /dev/null +++ b/docs/docs/howtos/HowTos.md @@ -0,0 +1,3 @@ +# HowTos + +This section provides a growing collection of HowTos for the ACME oneM2M CSE. \ No newline at end of file diff --git a/docs/docs/howtos/RaspberryPi.md b/docs/docs/howtos/RaspberryPi.md index 2a1a46e3..26ccdc78 100644 --- a/docs/docs/howtos/RaspberryPi.md +++ b/docs/docs/howtos/RaspberryPi.md @@ -12,34 +12,31 @@ First, we need to install a newer Python 3 runtime on our Raspberry Pi. The following download gets the source code from the official Python repository. It could be a newer version of Python as well, of course. -=== "Download Python" - ```sh - wget https://www.python.org/ftp/python/3.11.4/Python-3.11.4.tgz - ``` +```sh title="Download Python" +wget https://www.python.org/ftp/python/3.11.4/Python-3.11.4.tgz +``` ### Installing Extra Components The following commands install the necessary system libraries and other tools to compile Python on the Raspberry Pi. : -=== "Install Extra Components and Libraries" - ```sh - sudo apt update - sudo apt-get install -y build-essential tk-dev libncurses5-dev libncursesw5-dev libreadline6-dev libdb5.3-dev libgdbm-dev libsqlite3-dev libssl-dev libbz2-dev libexpat1-dev liblzma-dev zlib1g-dev libffi-dev libatlas-base-dev libgeos-dev gfortran git cmake libpq-dev - ``` +```sh title="Install Extra Components and Libraries" +sudo apt update +sudo apt-get install -y build-essential tk-dev libncurses5-dev libncursesw5-dev libreadline6-dev libdb5.3-dev libgdbm-dev libsqlite3-dev libssl-dev libbz2-dev libexpat1-dev liblzma-dev zlib1g-dev libffi-dev libatlas-base-dev libgeos-dev gfortran git cmake libpq-dev +``` ### Compile Python The next step is to unpack and to unpack, configure, make and install the Python runtime. -=== "Compile Python" - ```sh - tar -xzvf Python-3.11.4.tgz - cd Python-3.11.4/ - ./configure --enable-optimizations - sudo make -j 4 - sudo make altinstall - cd .. - ``` +```sh title="Compile Python" +tar -xzvf Python-3.11.4.tgz +cd Python-3.11.4/ +./configure --enable-optimizations +sudo make -j 4 +sudo make altinstall +cd .. +``` ## Install, Configure, and Run ACME diff --git a/docs/docs/howtos/StandAloneWebUI.md b/docs/docs/howtos/StandAloneWebUI.md new file mode 100644 index 00000000..5243b98e --- /dev/null +++ b/docs/docs/howtos/StandAloneWebUI.md @@ -0,0 +1,47 @@ +# Running the ACME Web UI as a Stand-Alone Application + +The web UI can also be run as an independent application. Since it communicates with the CSE via the Mca interfave it should be possible to use it with other CSE implementations as well as long as those third party CSEs follow the oneM2M http binding specification. It only supports the resource types that the ACME CSE supports, but at least it will present all other resource types as *unknown*. + +You can start the stand-alone web UI in a terminal like this (in the sub-directory [acme/webui](https://github.com/ankraft/ACME-oneM2M-CSE/tree/master/acme/webui)): + +```bash title="Start the Web UI as a stand-alone application" +python3 webUI.py +``` + +It starts with defaults, which can be set via command line arguments: + + +| Command Line Argument | Description | +|:----------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| -h, --help | Show a help message and exit. | +| --ip HOSTIP | Specify the web UI's local IP address to bind to.
Default: 127.0.0.1 (only localhost) | +| --port HOSTPORT | Specify the web UI's local port.
Default: 8000 | +| --cseurl TARGETURL | The target CSE's base URL. This is where the actual CSE can be reached.
Default: http://127.0.0.1:8080/ | +| --ri TARGETRI | The target CSE's default base RI.
Default: id-in | +| --originator TARGETORIGINATOR | The target CSE's default originator.
Default: CAdmin | +| **https/tls** | | +| --tls | Enable TLS (https) for the web UI.
It is disabled by default. | +| --certfile CERTFILE | Path to the certificate file for TLS.
Required for --tls.
Default: None. | +| --keyfile KEYFILE | Path to the private key file.
Required for --tls.
Default: None. | +| **OAuth** | | +| --oauth | Enable OAuth2 authentication for CSE access.
Default: False. | +| --oauth-server-url OAUTHSERVERURL | The OAuth2 server URL from which to retrieve the authentication token.
Default: None.
Automatic token retrieval and renewal is supported for *keycloak*. | +| --client-id CLIENTID | The OAuth2 client ID.
Default: None. | +| --client-secret CLIENTSECRET | The OAuth2 client secret.
Default: None. | +| **Misc** | | +| --logging | Enable logging to console.
Default: False | +| --no-open | Disable opening a web browser on startup.
Default: False | + + +## Example +The following command starts the web UI's proxy server with the following parameters: + +- The proxy binds to the local IP interface (127.0.0.1) on port *8000* (both the defaults). +- The proxy proxy serves the web UI via http (the default, no TLS is enabled). +- The remote CSE is reachable at *https://example.com/cse/* and has the resource ID *incse*. +- The remote CSE requires OAuth2 authentication, the client ID is *aClientID*, the client secret is *aClientSecret*, and the token is retrieved and renewed from *https://example.com/token*. +- Logging is enabled. + +```bash title="Start the Web UI as a stand-alone application with OAuth2" +python3 webUI.py --cseurl https://example.com/cse/ --logging --ri incse --oauth --client-id aClientID --client-secret aClientSecret --oauth-server-url https://example.com/token +``` \ No newline at end of file diff --git a/docs/docs/images/export_resource.png b/docs/docs/images/export_resource.png new file mode 100644 index 0000000000000000000000000000000000000000..30df374ce4734f4d6c648a5ca11edf46f75e0fac GIT binary patch literal 111758 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 &&XS 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 Xf_(ei+d_jkfIlg5d9ex+gMj}BNNpZt literal 0 HcmV?d00001 diff --git a/docs/docs/index.md b/docs/docs/index.md index ee2bed05..9189edbe 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -48,8 +48,8 @@ hide: The intention is to provide an easy to install and use, extensible, and maintainable oneM2M CSE. - TODO reference? - [:octicons-arrow-right-24: Reference](#) + [:octicons-arrow-right-24: Development Documentation](development/Overview.md)     [:octicons-arrow-right-24: HowTos](howtos/HowTos.md) + - :material-scale-balance:{ .lg .middle } **Open Source | BSD 3-Clause** diff --git a/docs/docs/neu/Importing.md b/docs/docs/neu/Importing.md deleted file mode 100644 index 531f08c7..00000000 --- a/docs/docs/neu/Importing.md +++ /dev/null @@ -1,391 +0,0 @@ -[← README](../README.md) - -# CSE Startup, Importing Resources and Other Settings - -[Initial Resources](#resources) -[Attribute and Hierarchy Policies for FlexContainer Specializations](#flexcontainers) -[Attribute Policies for Common Resources and Complex Types](#attributes) -[Help Documentation](#help-documentation) - - - -## Initial Resources - -During CSE startup and restart it is necessary to import a first set of resources to the CSE. This is done automatically by the CSE by running a script that has the [@init](ACMEScript-metatags.md#init) meta tag set. By default this is the [init.as](../init/init.as) script from the [init](../init) directory. - -Not much validation, access control, or registration procedures are performed when importing resources this way. - -See also [@init meta tag](ACMEScript-metatags.md#init) - -**Mandatory Resources to the CSE** - -Please note that importing is required for creating the CSEBase, the administration AE, and a general-access ACP resources. Those are imported before all other resources, so that the CSEBase resource can act as the root for the resource tree and the permissions for the admin originator are created. - -**Other Resources** - -Another option to import more resources automatically whenever the CSE starts or restarts is to have a script as an event handler for the *[onStartup](ACMEScript-metatags.md#onstartup)* and *[onRestart](ACMEScript-metatags.md#onrestart)* events. - - -TODO move to scripts -### Referencing Configuration Settings - -By using macros the initial resources can be kept independent from individual settings. -Most [configuration](Configuration.md) settings can be referenced and used by a simple macro mechanism. -For this a given macro name is enclosed by ```${...}```, e.g. ```${cse.cseID}```. -The following example shows the initial *CSEBase* resource definition from the *startup.as* script file: - -```list -(import-raw - (get-config "cse.originator") - {"m2m:cb": { - "ri": "${ get-config \"cse.resourceID\" }", - "rn": "${ get-config \"cse.resourceName\" }", - "csi": "${ get-config \"cse.cseID\" }", - "rr": true, - "csz": [ "application/json", "application/cbor" ], - "acpi": [ "${ get-config \"cse.cseID\" }/acpCreateACPs" ], - "poa": [ "${ get-config \"http.address\" }" ] - }}) -``` - -See the [documentation for scripts](ACMEScript.md). - - - -## FlexContainer Specializations Attribute and Hierarchy Policies - -The CSE uses attribute policies for validating the attributes of all supported resource types (internal to the *m2m* namespace). -But for all <flexContainer> specializations, e.g. for oneM2M's TS-0023 ModuleClasses, those attribute policies and the allowed <flexContainer> hierarchy must be provided. This can be done by adding attribute policy files for import. - -Those files are imported from the common import / init directory. More than one such file can be provided, for example one per domain. The files must have the extension ".fcp". - -### Format - -The format is a JSON structure that follows the structures described in the following codes. -Some of the fields are not yet used, but will supported by a future version of the CSE. - -```jsonc -// A file contains a list of Attribute Policies -attributePolicies = [ - - // Each Attribute Policy is an object - { - // The specialisation's namespace and short name. Mandatory. - "type" : "namespace:shortname", - - // The specialisation's long name. Optional, and for future developments. - "lname" : "attributePolicyLongname", - - // The specialisation's containerDefinition. Must be present for flexContainers, but can be empty to prevent warnings. - "cnd" : "containerDefinition", - - // The specialisation's SDT type. Could be "device", "subdevice", "moduleclass", or "action". Optional, and for future developments. - "sdttype" : "SDTcontainerType", - - // A list of attributes. Each entry specifies a single attribute of the specialization. Optional. - "attributes": [ - - // A single attribute is an object. - { - // The attribute's short name. Mandatory. - "sname" : "attributeShortName", - - // The attribute's long name. Optional, and for future developments. - "lname" : "attributeLongName", - - // The attribute's data type. Mandatory, and one from this list: - // - positiveInteger - // - nonNegInteger - // - unsignedInt - // - unsignedLong - // - string - // - timestamp - // - list - // - dict - any anonymous complex structure. This should be avoided and be replaced by a complex type name - // - adict (anonymous dict) - // - anyURI - // - boolean - // - enum - // - geoCoordinates - // - // In addition, the *attributeType* can be the name of any defined complex type. This - // complex type must be defined in any of the attribute policy files. - "type" : "attributeType", - - // The sub-type of a list type. - // This can be any of the types defined for *type*, or a complex type. - "ltype" : "type", - - // Definition of enumeration values. This can only be an integer value, or range definitions - // in the format "start..end" that evaluate to all the integer values of the given range. - "evalues" : [ 1, 2, "3..5", 6 ], - - // Definition of an enumeration type and an alternative to "evalues". - // This is an enumerated data type name that is referenced. - "etype" : "enumeratedDataType", - - // The "oc" field specifies the CREATE request optionality. Optional, and one from this list: - // - O : Optional provided (default) - // - M : Mandatory provided - // - NP : Not provided - "oc" : "O|M|NP", - - // The "ou" field specifies the UPDATE request optionality. Optional, and one from this list: - // - O : Optional provided (default) - // - M : Mandatory provided - // - NP : Not provided - "ou" : "O|M|NP", - - // The "od" field specifies the DISCOVERY request optionality. Optional, and one from this list: - // - O : Optional provided (default) - // - M : Mandatory provided - // - NP : Not provided - "od" : "O|M|NP", - - // The "annc" field specifies whether an announced optionality. Optional, and one from this list: - // - OA : Optional announced (default) - // - MA : Mandatory announced - // - NA : Not announced - "annc": "OA|MA|NA", - - // The attribute multiplicity. Optional, and one from this list: - // - 01 : The attribute is optional (default) - // - 01L : the attribute is an optional list - // - 1 : The attribute is mandatory - // - 1L : The attribute is a mandatory list - "car" : "01|01L|1|1L", - - // A list of oneM2M resource types that use this attribute. - // The folling special attribute types are also allowed: - // - ALL : This attribute definition is suitable for all resource types for which it is a member - // - REQRESP : This attribute definition is suitable for request and response type resources. - // This attribute is optional and used for the general attributePolicies, but not for flexContainers. - "rtypes" : [ ] - }, - - ], - - // A list of child resource types. Optional. - "children" : [ - // This list consists of one or more strings, each of those is the name of an additional - // child resource specialisation. It is not necessary to specify here the already allowed - // child resource types of . - ] - } -] -``` - -**Examples** - -The following examples show the attribute policies for the *binarySwitch* and *deviceLight* specialisations, both defined in oneM2M's TS-0023 specification. - -```jsonc -[ - // ModuleClass: binarySwitch (binSh) - { - "type" : "cod:binSh", - "lname" : "binarySwitch", - "cnd" : "org.onem2m.common.moduleclass.binarySwitch", - "attributes": [ - // DataPoint: dataGenerationTime - { "sname" : "dgt", "lname" : "dataGenerationTime", "type" : "timestamp", "car" : "01" }, - // DataPoint: powerState - { "sname" : "powSe", "lname" : "powerState", "type" : "boolean", "car" : "1" } - ] - }, - - // DeviceClass: deviceLight - { - "type" : "cod:devLt", - "lname" : "deviceLight", - "cnd" : "org.onem2m.common.device.deviceLight", - "children" : [ - "cod:fauDn", - "cod:binSh", - "cod:runSe", - "cod:color", - "cod:colSn", - "cod:brigs" - ] - } -] -``` - - -## Attribute Policies for Common Resources, Complex Types and Enum Types - -During startup the CSE reads the attribute policies for common resource types and complex type definitions from the files with the extension ".ap" in the *init* directory, for example [attributePolicies.ap](../init/attributePolicies.ap). More than one attribute file can be defined. -The attributes for attribute policies are the same as for the <flexContainer> attribute definitions above. - -In addition, enumeration types are defined in files with the extension ".ep". - -### Formats - -The format is a JSON structure that follows the structure described in the following code. - -```jsonc -// The attributePolicy.ap file contains a dictionary of AttributePolicies -{ - - // Each Attribute Policy is identified for an attribute's short name - "attributeShortname": [ - - // A list of attribute policies may be defined for each short name. - // There might be some definitions for the same attribute that are defined - // slighly from each other. Therefore, a list of resource types is given - // for each definition for which that definition is valid. - // See the attributes definition for flexContainer attribute policies above. - - { - // The rtypes definition is mandatory here. It defines a list of oneM2M - // resource types for which this definition is valid. This way slight - // differences in attributes in some resource can be distinguished. - // The special names "ALL" (short for all resource types), "REQRESP" - // (for attributes in requests and responses), and "COMPLEX" (for an attribute - // that belongs to a complex type definition) can be used accordingly. - "rtype" : [ "", "" ], - - // The name of a complex type this attribute defintion belongs to. This - // attribute is only be present in an attribute policy definition when - // "rtype" is set to "COMPLEX". - "ctype" : "", - - // The other definitions that can be used are (see above for details): - // - "lname" : ... - "ns" : ... - "type" : ... - "ltype" : ... - "etype" : ... - "evalues" : ... - "car" : ... - "oc" : ... - "ou" : ... - "od" : ... - "annc" : ... - } - ] -} -``` - -The format for enumeration data type definitions is a bit simpler: - -```jsonc -// The attributePolicy.ep file contains a dictionary of enumeration data types -{ - - // Each enumeration definition is identified by its name. It is a dictionary. - "enumerationType": { - - // A single enumeration definition is key value pair. The key is the enumeration - // value, the value is the interpretation of that value. - "" : "" - - // This defines a range of values. Each one gets the same interpretation assigned. - ".." : "" - } -} -``` - -**Example** - -The following gives an example for the attribute *ty* (*resourceType*). - -```jsonc -{ - "rn": [ - { - "rtypes": [ "ALL" ], - "lname": "resourceName", - "ns": "m2m", - "type": "string", - "car": "1", - "oc": "O", - "ou": "NP", - "od": "O", - "annc": "NA" - } - ], - "ty": [ - { - "rtypes": [ "ALL" ], - "lname": "resourceType", - "ns": "m2m", - "type": "enum", - "etype": "m2m:resourceType", - "car": "1", - "oc": "NP", - "ou": "NP", - "od": "O", - "annc": "NA" - } - ] -} -``` - -**Complex Type Attribute** - -The following example shows the definition of the attribute *operator* (optr) that -belongs to the complex type *m2m:evalCriteria*. - -```jsonc -{ - "optr": [ - { - "rtypes": [ "COMPLEX" ], - "ctype": "m2m:evalCriteria", - "lname": "operator", - "ns": "m2m", - "type": "enum", - "etype": "m2m:evalCriteriaOperator", - "car": "1" - } - ] -} -``` - -**Enumeration Data Type** - -The following example show the definition for the enumeration data types used in the examples above. - -```jsonc -{ - "m2m:evalCriteriaOperator" : { - "evalues": [ "1..6" ] - }, - "m2m:resourceType" : { - "evalues" : [ "1..5", 9, "13..17", 23, 24, "28..30", 48, 58, 60, 65, - "10001..10005", 10009, "10013..10014", 10016, "10028..10030", 10060, 10065 ] - } -} -``` - - - -## Help Documentation - -Some CSE components provide a markdown documentation to the user, such as the Text UI. That documentation is imported from the -[init](../init) directory as well. The file extension for documentation files is ".docmd". - -In the documentation file individual sections are separated by markdown level 1 headers where the header title is the help topic -for the following section, which is a markdown text block with the help text. - -**Example** - -```markdown -# Topic 1 - -Some help text for topic 1. - -## Help sub section - -# Topic 2 -... -``` - - - - - - -[← README](../README.md) diff --git a/docs/docs/setup/Configuration-basic.md b/docs/docs/setup/Configuration-basic.md index 87a5b6b0..84095a10 100644 --- a/docs/docs/setup/Configuration-basic.md +++ b/docs/docs/setup/Configuration-basic.md @@ -15,23 +15,23 @@ When creating the configuration file, it is recommended to use the [interactive These are the general settings for the CSE. Some settings are mandatory, others are optional. This depends on the type of CSE to run. -| Setting | Description | Optional | -|:-----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------| -| cseType | The type of CSE to run.
Allowed values: `IN`, `MN`, `ASN` | No | -| cseID | The CSE-ID of the CSE. This is a unique identifier for the CSE. | No | -| cseName | The name of the CSE. | No | -| adminID | The CSE-ID of the CSE's admin. | No | -| networkInterface | The network interface to use. | No | -| cseHost | The IP address of the CSE.
The default is [${hostIPAddress}](../setup/Configuration-introduction.md#built-in-configuration-settings). | No | -| httpPort | The port for the HTTP server.
This value depends on the *cseType*. | No | -| registrarCseID | The CSE-ID of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*. | Yes | -| registrarCseName | The name of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*. | Yes | -| registrarCseHost | The IP address of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*.
The default is [${hostIPAddress}](../setup/Configuration-introduction.md#built-in-configuration-settings). | Yes | -| registrarCsePort | The port of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*. | Yes | -| databaseType | The type of database to use.
Allowed values: `memory`, `tinydb`, `postgresql` | No | -| logLevel | The log level for the CSE.
Allowed values: `debug`, `info`, `warning`, `error`, `off` | Yes | -| consoleTheme | The theme for the console and text UI.
Allowed values: `light`, `dark` | Yes | - -In addition to the settings in the table above, the [built-in configuration settings](../setup/Configuration-introduction.md#built-in-configuration-settings) +| Setting | Description | Optional | +|:-----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------| +| cseType | The type of CSE to run.
Allowed values: `IN`, `MN`, `ASN` | No | +| cseID | The CSE-ID of the CSE. This is a unique identifier for the CSE. | No | +| cseName | The name of the CSE. | No | +| adminID | The CSE-ID of the CSE's admin. | No | +| networkInterface | The network interface to use. | No | +| cseHost | The IP address of the CSE.
The default is [${hostIPAddress}](../setup/Configuration-introduction.md#command-line-arguments). | No | +| httpPort | The port for the HTTP server.
This value depends on the *cseType*. | No | +| registrarCseID | The CSE-ID of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*. | Yes | +| registrarCseName | The name of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*. | Yes | +| registrarCseHost | The IP address of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*.
The default is [${hostIPAddress}](../setup/Configuration-introduction.md#built-in-settings). | Yes | +| registrarCsePort | The port of the registrar CSE.
This setting is mandatory for *cseType* = *MN* and *ASN*. | Yes | +| databaseType | The type of database to use.
Allowed values: `memory`, `tinydb`, `postgresql` | No | +| logLevel | The log level for the CSE.
Allowed values: `debug`, `info`, `warning`, `error`, `off` | Yes | +| consoleTheme | The theme for the console and text UI.
Allowed values: `light`, `dark` | Yes | + +In addition to the settings in the table above, the [built-in configuration settings](../setup/Configuration-introduction.md#built-in-settings) and [envirnoment variables](../setup/Configuration-introduction.md#interpolation-of-environment-variables) can be used in the configuration. diff --git a/docs/docs/setup/Configuration-introduction.md b/docs/docs/setup/Configuration-introduction.md index 2df0cbae..896fff87 100644 --- a/docs/docs/setup/Configuration-introduction.md +++ b/docs/docs/setup/Configuration-introduction.md @@ -57,11 +57,10 @@ There are some built-in configuration settings that can be used in the configura : Two built-in configuration settings that point to acme's main *init* directory. Both settings are equivalent and can be used interchangeably. - === "Example" - ```ini - [cse] - resourcesPath=${basic.config:initDirectory} - ``` + ```ini title="Use built-in settings" + [cse] + resourcesPath=${basic.config:initDirectory} + ``` **${basic.config:moduleDirectory}** **${moduleDirectory}** @@ -77,8 +76,7 @@ Environment variables can be used in the configuration file to provide sensitive Another useful application is to provide the IP address of a Docker host to the CSE. This can be done, for example, by setting the environment variable `DOCKER_HOST_IP` and using it in the configuration file. -=== "Example" - ```ini - [basic.config] - cseHost=${DOCKER_HOST_IP} - ``` +```ini title="Use Environment Variable to set Host IP" +[basic.config] +cseHost=${DOCKER_HOST_IP} +``` diff --git a/docs/docs/setup/Console.md b/docs/docs/setup/Console.md index 209b9661..5f858bdd 100644 --- a/docs/docs/setup/Console.md +++ b/docs/docs/setup/Console.md @@ -68,11 +68,10 @@ Simple wildcards are allowed in this setting. Example to hide all resources with resource identifiers starting with 'acp': -=== "Hide resources" - ```ini - [cse.console] - hideResources=acp* - ``` +```ini title="Hide Resources" +[cse.console] +hideResources=acp* +``` ## Supported Function Keys diff --git a/docs/docs/setup/Database.md b/docs/docs/setup/Database.md index 171abe52..9e6f254a 100644 --- a/docs/docs/setup/Database.md +++ b/docs/docs/setup/Database.md @@ -10,8 +10,10 @@ The database files are stored by default in the directory *{baseDirectory}/data* You enable the TinyDB database by setting the *databaseType* setting in the *\[basic.config\]* section to *tinydb*: - [basic.config] - databaseType=tinydb +```ini title="Enable TinyDB as database" +[basic.config] +databaseType=tinydb +``` ## TinyDB In-Memory @@ -19,8 +21,11 @@ TinyDB also provides a memory-based database that might be useful for testing an You enable the in-memory database by setting the *databaseType* setting in the *\[basic.config\]* section to *memory*: - [basic.config] - databaseType=memory +```ini title="Enable in-memory database" +[basic.config] +databaseType=memory +``` + ## PostgreSQL @@ -32,20 +37,26 @@ The following steps describe how to set up a PostgreSQL database for the ACME CS 1. Create a new database and user for the CSE. It is recommended to use the CSE-ID as the database name and as the role name. For example, you can use the following commands to create a new database named *id-in* and a role named *id-in* with the password *acme*: - psql -c "CREATE DATABASE \"id-in\";" - psql -c "CREATE USER \"id-in\" WITH PASSWORD 'acme';" - psql -c "GRANT ALL PRIVILEGES ON DATABASE \"id-in\" TO \"id-in\";" +```bash title="Create database and role" +psql -c "CREATE DATABASE \"id-in\";" +psql -c "CREATE USER \"id-in\" WITH PASSWORD 'acme';" +psql -c "GRANT ALL PRIVILEGES ON DATABASE \"id-in\" TO \"id-in\";" +``` 1. If not done during the setup procedure above: Edit the *acme.ini* configuration file and the following settings under the *\[database.postgresql\]* section: + ```ini title="PostgreSQL database settings" [database.postgresql] password = acme + ``` All other settings are optional and can be left at their default values. The *database* and *role* settings are set to the CSE-ID by default. If you used different names for the database and role, you have to adjust these settings accordingly. Also the *host* and *port* settings are set to *localhost* and *5432* by default. If your PostgreSQL server is running on a different host or port, you have to adjust these settings as well. You also need to enable the PostgreSQL database by setting the *databaseType* setting in the *\[basic.config\]* section to *postgresql*: + ```ini title="Enable postgreSQL database" [basic.config] databaseType=postgresql + ``` 1. Run the CSE. The database schema, tables and other structures are created automatically by the CSE when it starts and connects for the first time. @@ -57,9 +68,13 @@ Sometimes it may not be possible or desirable to use a PostgreSQL database, for In this case, you can disable the PostgreSQL database by setting the *databaseType* setting in the *\[basic.config\]* section to *tinydb* or *memory*: - [basic.config] - databaseType=tinydb +```ini title="Disable PostgreSQL database" +[basic.config] +databaseType=tinydb +``` In order to prevent the PostgreSQL Python modules (i.e. psycopg2) to be loaded you can also set the `ACME_NO_PGSQL` environment variable to any value before running the CSE: - export ACME_NO_PGSQL=1 +```bash title="Disable PostgreSQL Database Support in Environment" +export ACME_NO_PGSQL=1 +``` diff --git a/docs/docs/setup/Installation.md b/docs/docs/setup/Installation.md index b8ad542c..158ff06d 100644 --- a/docs/docs/setup/Installation.md +++ b/docs/docs/setup/Installation.md @@ -8,7 +8,6 @@ ACME requires **Python 3.10** or newer. Install it with your favorite package ma You may consider to use a virtual environment manager like pyenv + virtualenv (see [this HowTo](https://github.com/ankraft/ACME-oneM2M-CSE/discussions/137) or [this tutorial](https://realpython.com/python-virtual-environments-a-primer/){target=_new}). - ## Installation and First Setup ### Installation @@ -19,24 +18,32 @@ There are two ways to install the ACME CSE: using *pip* or by doing a manual ins Run *pip* to install the ACME CSE from the Python Package Index (PyPI): - python -m pip install acmecse + ```bash title="Install ACME CSE" + python -m pip install acmecse + ``` This will install the latest version of the ACME CSE and all required dependencies. You can also upgrade to the latest version by running: - python -m pip install --upgrade acmecse + ```bash title="Upgrade ACME CSE" + python -m pip install --upgrade acmecse + ``` === "Manual Installation" - 1. Install the ACME CSE by cloning the repository, or by downloading the [latest](https://github.com/ankraft/ACME-oneM2M-CSE/releases/latest){target=_new} release package, unpacking it, and copying the whole distribution to a new directory. + 1. Install the ACME CSE by cloning the repository, or by downloading the [latest](https://github.com/ankraft/ACME-oneM2M-CSE/releases/latest){target=_new} release package, unpacking it, and copying the whole distribution to a new directory. - git clone https://github.com/ankraft/ACME-oneM2M-CSE.git - cd ACME-oneM2M-CSE + ```bash title="Clone the Repository" + git clone https://github.com/ankraft/ACME-oneM2M-CSE.git + cd ACME-oneM2M-CSE + ``` 1. It is recommend to install the required packages by running the following command: - python3 -m pip install -r requirements.txt + ```bash title="Install Required Packages" + python3 -m pip install -r requirements.txt + ``` You may also install the packages manually, but make sure to install the exact versions as specified in the *requirements.txt* file. @@ -54,13 +61,17 @@ You can start the CSE by simply running it from the command line: Run the following command from the command line from **within any directory that uses the Python environment where you installed the package**: - acmecse + ```bash title="Start ACME CSE" + acmecse + ``` === "For Manual Installation" Run the following command from the command line from **within the directory where you installed the CSE**: - python3 -m acme + ```bash title="Start ACME CSE as a module" + python3 -m acme + ``` Please refer to the [Running](Running.md) documentation for more detailed instructions how to start and run the ACME CSE. @@ -75,7 +86,6 @@ You can start the CSE by simply running it from the command line:
ACME CSE Guided Configuration
- After the configuration is saved, the CSE is started. with this configuration. 1. After terminating the CSE again you can edit that configuration file and add more settings if necessary. diff --git a/docs/docs/setup/Operation-diagrams.md b/docs/docs/setup/Operation-diagrams.md index da2906c6..c4315e5e 100644 --- a/docs/docs/setup/Operation-diagrams.md +++ b/docs/docs/setup/Operation-diagrams.md @@ -6,10 +6,9 @@ The CSE can generate a diagram with an overview about the hosted resource tree and the current deployment infrastructure of remote CSEs. This is available by sending a GET request as follows: -=== "GET request" - ```bash - curl localhost:8080/__structure__ - ``` +```bash title="GET request to retrieve the diagram script" +curl localhost:8080/__structure__ +``` This returns a PlantUML diagram script that can be rendered with the [PlantUML](https://plantuml.com){target=_new} tool. The diagram shows the resource tree and the deployment infrastructure of remote CSEs. The diagram can be used to get an overview of the current deployment and to identify potential issues. @@ -30,14 +29,13 @@ When enabled the http server creates an additional endpoint */\_\_structure__*. A similar text representation of the resource tree only can be retrieved from the endpoint */\_\_structure__/text* . -=== "GET request" - ```bash - curl localhost:8080/__structure__/text - ... +```bash title="GET Request to retrieve the text representation of the resource tree" +curl localhost:8080/__structure__/text +... - cse-in -> m2m:cb (csi=/id-in) | ri=id-in - ├── acpCreateACPs -> m2m:acp | ri=acpCreateACPs - ├── CAdmin -> m2m:ae | ri=CAdmin - ... - ``` +cse-in -> m2m:cb (csi=/id-in) | ri=id-in +├── acpCreateACPs -> m2m:acp | ri=acpCreateACPs +├── CAdmin -> m2m:ae | ri=CAdmin +... +``` diff --git a/docs/docs/setup/Operation-uppertester.md b/docs/docs/setup/Operation-uppertester.md index 529e163d..72bc5de4 100644 --- a/docs/docs/setup/Operation-uppertester.md +++ b/docs/docs/setup/Operation-uppertester.md @@ -18,7 +18,7 @@ The following sections present an overview. ### HTTP Header X-M2M-UTCMD : Run CSE Commands The `X-M2M-UTCMD` http header field is used to run a command internally by the CSE. The ACME CSE implements these commands -as [scripts](../development/ACMEScript.md) that have the meta tag [@uppertester](ACMEScript-metatags.md#uppertester) set. +as [scripts](../development/ACMEScript.md) that have the meta tag [@uppertester](../development/ACMEScript-metatags.md#uppertester) set. The following commands are available by default, but other can be added. Some of these commands are used to reconfigure the CSE when running test cases. @@ -45,39 +45,36 @@ In case a command returns a result then it is available in the header field `X-M This example initiates a reset of the CSE. The successful execution is indicated by the Response Status Code header *X-M2M-RSC: 2000* -=== "Resetting the CSE" - ```http - $ curl -X POST -H "X-M2M-UTCMD:Reset" http://localhost:8080/__ut__ - ... - < HTTP/1.1 200 OK - < Server: Werkzeug/3.0.2 Python/3.11.7 - < Date: Sun, 05 May 2024 11:09:33 GMT - < Server: ACME 2024.DEV - < X-M2M-RSC: 2000 - < X-M2M-UTRSP: false - < Content-Type: text/plain; charset=utf-8 - < Content-Length: 0 - < Connection: close - ... - ``` +```http title="Resetting the CSE" +$ curl -X POST -H "X-M2M-UTCMD:Reset" http://localhost:8080/__ut__ +... +< HTTP/1.1 200 OK +< Server: Werkzeug/3.0.2 Python/3.11.7 +< Date: Sun, 05 May 2024 11:09:33 GMT +< Server: ACME 2024.DEV +< X-M2M-RSC: 2000 +< X-M2M-UTRSP: false +< Content-Type: text/plain; charset=utf-8 +< Content-Length: 0 +< Connection: close +... +``` ### Get the CSE Status This example requests the CSE status. It is returned in the `X-M2M-UTRSP` header. -=== "Get the CSE Status" - - ```http - $ curl -v -X POST -H "X-M2M-UTCMD:Status" http://localhost:8080/__ut__ - ... - < HTTP/1.1 200 OK - < Server: Werkzeug/3.0.2 Python/3.11.7 - < Date: Sun, 05 May 2024 11:02:47 GMT - < Server: ACME 2024.04 - < X-M2M-RSC: 2000 - < X-M2M-UTRSP: RUNNING - < Content-Type: text/plain; charset=utf-8 - < Content-Length: 0 - < Connection: close - ... - ``` +```http title="Get the CSE Status" +$ curl -v -X POST -H "X-M2M-UTCMD:Status" http://localhost:8080/__ut__ +... +< HTTP/1.1 200 OK +< Server: Werkzeug/3.0.2 Python/3.11.7 +< Date: Sun, 05 May 2024 11:02:47 GMT +< Server: ACME 2024.04 +< X-M2M-RSC: 2000 +< X-M2M-UTRSP: RUNNING +< Content-Type: text/plain; charset=utf-8 +< Content-Length: 0 +< Connection: close +... +``` diff --git a/docs/docs/setup/Running.md b/docs/docs/setup/Running.md index 07b5504a..95b141b7 100644 --- a/docs/docs/setup/Running.md +++ b/docs/docs/setup/Running.md @@ -8,11 +8,15 @@ You can start the CSE by simply running it from the command line. This is the si === "Package installation" - acmecse + ```bash title="Start ACME CSE" + acmecse + ``` === "Manual Installation" - python3 -m acme + ```bash title="Start ACME CSE as a module" + python3 -m acme + ``` The current working directory is used as the base directory for the CSE and the *acme.ini* [configuration file](Configuration.md) must be in the same directory. An [interactive configuration process](Installation.md#guided-configuration) is started if the configuration file is not found. @@ -23,11 +27,15 @@ The CSE can also be started with a different configuration file: === "Package Installation" - acmecse --config + ```bash title="Start ACME CSE with a different configuration file" + acmecse --config + ``` === "Manual Installation" - python3 -m acme --config + ```bash title="Start ACME CSE with a different configuration file" + python3 -m acme --config + ``` The current working directory is still the base directory for the CSE and the configuration file is still expected to be located in this directory. @@ -37,11 +45,15 @@ The CSE can also be started with a different base directory: === "Package Installation" - acmecse -dir + ```bash title="Start ACME CSE with a different base directory" + acmecse -dir + ``` === "Manual Installation" - python3 -m acme -dir + ```bash title="Start ACME CSE with a different base directory" + python3 -m acme -dir + ``` This will use the specified directory as the root directory for runtime data such as *data*, *logs*, and *temporary* files. The configuration file *acme.ini*is expected to be in the specified directory, or it will be created there if it does not exist. @@ -69,7 +81,7 @@ The ACME CSE provides a number of command line arguments that will override the | --http-port <http port> | Specify the CSE's http server port.
This overrides the [address](../setup/Configuration-http.md#general-settings) configuration setting. | | --init-directory <directory> | Specify the import directory.
This overrides the [resourcesPath](../setup/Configuration-cse.md#general-settings) configuration setting. | | --network-interface <ip address | Specify the network interface/IP address to bind to.
This overrides the [listenIF](../setup/Configuration-http.md#general-settings) configuration setting. | -| --log-level {info, error, warn, debug, off} | Set the log level, or turn logging off.
This overrides the [level](../setup/Configuration-logging.md#configuration---logging) configuration setting. | +| --log-level {info, error, warn, debug, off} | Set the log level, or turn logging off.
This overrides the [level](../setup/Configuration-logging.md#general-settings) configuration setting. | | --mqtt, --no-mqtt | Enable or disable the MQTT binding.
This overrides the [mqtt.enable](../setup/Configuration-mqtt.md#general-settings) configuration setting. | | --remote-cse, --no-remote-cse | Enable or disable remote CSE connections and checking.
This overrides the [enableRemoteCSE](../setup/Configuration-cse.md#general-settings) configuration setting. | | --statistics, --no-statistics | Enable or disable collecting CSE statistics.
This overrides the [enable](../setup/Configuration-cse.md#statistics) configuration setting. | @@ -77,13 +89,6 @@ The ACME CSE provides a number of command line arguments that will override the | --ws, --no-ws | Enable or disable the WebSocket binding.
This overrides the [websocket.enable](../setup//Configuration-ws.md#general-settings) configuration setting. | -TODO: To Development - - -## Debug Mode - -Please see [Development - Debug Mode](Development.md#debug-mode) how to enable the debug mode to see further information in case you run into problems when trying to run the CSE. - ## Stopping the CSE @@ -96,6 +101,7 @@ Please note, that the shutdown might take a moment (e.g. gracefully terminating **Being impatient and hitting *CTRL-C* twice might lead to data corruption.** + TODO: To Development diff --git a/docs/docs/setup/WebUI.md b/docs/docs/setup/WebUI.md index 6e21715f..4accf7b2 100644 --- a/docs/docs/setup/WebUI.md +++ b/docs/docs/setup/WebUI.md @@ -18,53 +18,4 @@ The web UI also provides a REST UI where you can send REST requests directed at
ACME CSE Web UI with REST Interface
-Move the following to a tool description -## Run the ACME Web UI as a Stand-Alone Application - -The web UI can also be run as an independent application. Since it communicates with the CSE via the Mca interfave it should be possible to use it with other CSE implementations as well as long as those third party CSEs follow the oneM2M http binding specification. It only supports the resource types that the ACME CSE supports, but at least it will present all other resource types as *unknown*. - -You can start the stand-alone web UI in a terminal like this (in the sub-directory [acme/webui](https://github.com/ankraft/ACME-oneM2M-CSE/tree/master/acme/webui): - -```bash -python3 webUI.py -``` - -It starts with defaults, which can be set via command line arguments - - - -| Command Line Argument | Description | -|:----------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| -h, --help | Show a help message and exit. | -| --ip HOSTIP | Specify the web UI's local IP address to bind to.
Default: 127.0.0.1 (only localhost) | -| --port HOSTPORT | Specify the web UI's local port.
Default: 8000 | -| --cseurl TARGETURL | The target CSE's base URL. This is where the actual CSE can be reached.
Default: http://127.0.0.1:8080/ | -| --ri TARGETRI | The target CSE's default base RI.
Default: id-in | -| --originator TARGETORIGINATOR | The target CSE's default originator.
Default: CAdmin | -| **https/tls** | | -| --tls | Enable TLS (https) for the web UI.
It is disabled by default. | -| --certfile CERTFILE | Path to the certificate file for TLS.
Required for --tls.
Default: None. | -| --keyfile KEYFILE | Path to the private key file.
Required for --tls.
Default: None. | -| **OAuth** | | -| --oauth | Enable OAuth2 authentication for CSE access.
Default: False. | -| --oauth-server-url OAUTHSERVERURL | The OAuth2 server URL from which to retrieve the authentication token.
Default: None.
Automatic token retrieval and renewal is supported for *keycloak*. | -| --client-id CLIENTID | The OAuth2 client ID.
Default: None. | -| --client-secret CLIENTSECRET | The OAuth2 client secret.
Default: None. | -| **Misc** | | -| --logging | Enable logging to console.
Default: False | -| --no-open | Disable opening a web browser on startup.
Default: False | - - -### Example -The following command starts the web UI's proxy server with the following parameters: - -- The proxy binds to the local IP interface (127.0.0.1) on port *8000* (both the defaults). -- The proxy proxy serves the web UI via http (the default, no TLS is enabled). -- The remote CSE is reachable at *https://example.com/cse/* and has the resource ID *incse*. -- The remote CSE requires OAuth2 authentication, the client ID is *aClientID*, the client secret is *aClientSecret*, and the token is retrieved and renewed from *https://example.com/token*. -- Logging is enabled. - -  - - python3 webUI.py --cseurl https://example.com/cse/ --logging --ri incse --oauth --client-id aClientID --client-secret aClientSecret --oauth-server-url https://example.com/token diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e8f5f1c3..3200ce9a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -122,9 +122,10 @@ extra: nav: - Home: - - 'index.md' + - 'Home': 'index.md' - 'Supported Features': 'home/Supported.md' - 'Roadmap': 'home/Roadmap.md' + - 'Acknowledgements': 'home/Acknowledgements.md' - 'LICENSE': 'home/License.md' - 'Contact': 'home/Contact.md' - 'Setup & Running': @@ -154,6 +155,12 @@ nav: - 'Development': - 'Overview': 'development/Overview.md' - 'Unit Tests': 'development/UnitTests.md' + - 'Type Checking': 'development/TypeChecking.md' + - 'Start-Up and Policies': + - 'Start-Up Resources': 'development/StartupResources.md' + - 'Attribute Policies': 'development/AttributePolicies.md' + - 'FlexContainer Policies': 'development/FlexContainerPolicies.md' + - 'Help File Format': 'development/HelpDocumentation.md' - 'ACMEScript': - 'Introduction': 'development/ACMEScript.md' - 'Loading & Running Scripts': 'development/ACMEScript-loading.md' @@ -162,10 +169,18 @@ nav: - 'Variables': 'development/ACMEScript-variables.md' - 'Meta Tags': 'development/ACMEScript-metatags.md' - 'Upper Tester Integration': 'development/ACMEScript-uppertester.md' + - 'Integrating ACME CSE': 'development/Integrating_ACME.md' + - 'Debug Mode': 'development/DebugMode.md' + - 'Tools': + - 'Notification Server': 'development/NotificationServer.md' - 'HowTos': + - 'Overview': 'howtos/HowTos.md' - 'Docker': 'howtos/Docker.md' + - 'Experimental WebSocket Binding': 'howtos/ExperimentalWebSocketBinding.md' + - 'Export Resources': 'howtos/ExportResources.md' - 'Pyenv Setup': 'howtos/HowTo-pyenv.md' - 'Raspberry Pi': 'howtos/RaspberryPi.md' + - 'Stand-alone Web UI': 'howtos/StandAloneWebUI.md' - 'Help': - 'FAQ': 'help/FAQ.md' - 'Contributing': 'help/Contributing.md'