├── .gitignore ├── .gitlab-ci.yml ├── .gitmodules ├── .travis.yml ├── .vscode └── extensions.json ├── README.md ├── docs └── CASBACnetExplorer.png ├── include └── README ├── lib ├── README └── cas-bacnet-stack │ └── src │ └── README ├── platformio.ini ├── src ├── CIBuildSettings.h └── main.cpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | 8 | lib/cas-bacnet-stack/ 9 | 10 | \.vscode/settings\.json 11 | 12 | \.vscode/ipch/ 13 | 14 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://docs.platformio.org/en/latest/ci/gitlab.html 2 | 3 | variables: 4 | GIT_SUBMODULE_STRATEGY: recursive 5 | 6 | stages: 7 | - build 8 | 9 | cache: 10 | key: ${CI_JOB_NAME} 11 | 12 | ESP32-BACnetServerExample: 13 | image: python:2.7 14 | stage: build 15 | tags: 16 | - docker 17 | before_script: 18 | # Update the build number 19 | - echo -e "#define CI_PIPELINE_IID $CI_PIPELINE_IID\n" >src/CIBuildSettings.h 20 | - "cat src/CIBuildSettings.h" 21 | 22 | # Install platformio. (Requires Python 2.7, see image above) 23 | - "pip install -U platformio" 24 | script: 25 | # Move the files 26 | # The way that platforum.io works is that it will compile every file in the lib folder. 27 | # and the entire cas-bacnet-stack has many dependencies, including unit testing frameworks. 28 | # so we can't just put the entire cas-bacnet-stack folder into the lib folder. We need to 29 | # move the files that are needed into lib folder for the build. 30 | - mkdir lib/cas-bacnet-stack/ 31 | - cp cas-bacnet-stack/source/* lib/cas-bacnet-stack/ 32 | - cp cas-bacnet-stack/adapters/cpp/* lib/cas-bacnet-stack/ 33 | - cp cas-bacnet-stack/submodules/cas-common/source/* lib/cas-bacnet-stack/ 34 | 35 | # Build 36 | # More info about platformio https://docs.platformio.org/en/latest/userguide/cmd_ci.html 37 | - "platformio ci --lib='lib/cas-bacnet-stack' --board=featheresp32 src | tee output.txt" 38 | 39 | # Memory usage metrics 40 | - echo "CI_PIPELINE_IID $CI_PIPELINE_IID" 41 | - grep "^DATA" output.txt >> metrics.txt 42 | - grep "^PROGRAM" output.txt >> metrics.txt 43 | 44 | # After the project builds I could not find the .pioenvs in the docker image. 45 | # this folder does exist on my local computer during testing just not in the 46 | # docker image. The artifacts are not required because this is just to ensure 47 | # that changes can be built without error. There is no unit tests, or production 48 | # tests happening on this binary. 49 | # Still strange that I could not get the binary. 50 | # 51 | # artifacts: 52 | # paths: 53 | # - .pioenvs/featheresp32/firmware*.* 54 | artifacts: 55 | paths: 56 | - metrics.txt 57 | reports: 58 | metrics: metrics.txt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cas-bacnet-stack"] 2 | path = cas-bacnet-stack 3 | url = ../../chipkin/cas-bacnet-stack.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 BACnet Server Example 2 | 3 | In this project we are using the [CAS BACnet stack](https://www.bacnetstack.com/) to generate a simple BACnet server with one Multi-state-value (MSV) object. The MSV shows the current mode of the built-in LED. A BACnet client (such as the [CAS BACnet Explorer](https://store.chipkin.com/products/tools/cas-bacnet-explorer)) can be used to write to this MSV to change the mode. 4 | 5 | The MSV allows for three possaible values to be written. 6 | 7 | - 1 = **Off** 8 | - 2 = **On** 9 | - 3 = **Blink** - Blinks the LED on and off at a rate of 500 ms. 10 | 11 | ## Quick start 12 | 13 | 1. Download and install [Platform/io](https://platformio.org/) for [Visual studios code](https://code.visualstudio.com/) 14 | 2. Add the ESP32 board to Platform/io 15 | 3. Move the [CAS BACnet stack](https://www.bacnetstack.com/) source and adapter files to the */lib/src/* folder: 16 | ``` 17 | From: 18 | |--lib 19 | | | 20 | | |--cas-bacnet-stack 21 | | | |--adapters 22 | | | | |--cpp 23 | | | | | |- *.cpp 24 | | | | | |- *.h 25 | | | |--source 26 | | | | |- *.cpp 27 | | | | |- *.h 28 | | | |--submodules 29 | | | | |--cas-common 30 | | | | | |--source 31 | | | | | | |- *.cpp 32 | | | | | | |- *.c 33 | |--src 34 | | |- main.c 35 | 36 | To: 37 | |--lib 38 | | | 39 | | |--cas-bacnet-stack 40 | | | |--src 41 | | | | |- *.cpp 42 | | | | |- *.h 43 | |--src 44 | | |- main.c 45 | ``` 46 | 4. Use Platform/io to *Build* the project. 47 | 5. Use Platform/io to *Upload and Monitor*. 48 | 49 | ## Supported BIBBs 50 | 51 | The CAS BACnet stack supports many other BIBBs, this minumial example only supports the following: 52 | 53 | - **DS-RP-B**: Data Sharing - ReadProperty-B 54 | - **DS-RPM-B**: Data Sharing - ReadPropertyMultiple-B 55 | - **DS-WP-B**: Data Sharing - WriteProperty-B 56 | - **DM-DDB-B**: Device and Network Management - Dynamic Device Binding-B (Who-Is) 57 | - **DM-DOB-B**: Device and Network Management - Dynamic Object Binding-B (Who-Has) 58 | 59 | ## BACnet objects supported 60 | 61 | The CAS BACnet stack supports many other object types this minumial example only supports the following: 62 | 63 | - Device (8) 64 | - Multi-state Value (19) 65 | 66 | ## Device Tree 67 | 68 | Below is the device tree exported from the CAS BACnet Explorer. 69 | 70 | ```txt 71 | device: 389001 (ESP32 BACnet Example Server) 72 | object_identifier: device (389001) 73 | object_type: device 74 | vendor_identifier: Chipkin Automation Systems (0x185) 75 | application_software_version: v1 76 | firmware_revision: 3.25.0.0 77 | model_name: CAS BACnet Stack 78 | object_name: ESP32 BACnet Example Server 79 | protocol_services_supported: readProperty (1), readPropertyMultiple (1), writeProperty (1), who_Has (1), who_Is (1), 80 | protocol_version: 1 81 | vendor_name: Chipkin Automation Systems 82 | protocol_revision: 14 83 | multi_state_value: 1 (LED State) 84 | object_identifier: multi_state_value (1) 85 | object_type: multi_state_value (0x13) 86 | event_state: normal (0x0) 87 | number_of_states: 3 88 | object_name: LED State 89 | out_of_service: False 90 | present_value: 3 91 | status_flags: in_alarm (0), fault (0), overridden (0), out_of_service (0), 92 | state_text: Off, On, Blink 93 | 94 | ``` 95 | 96 | ## Example in the CAS BACnet Explorer 97 | 98 | ![Preview of the CAS BACnet Explorer](/docs/CASBACnetExplorer.png?raw=true "Preview of the CAS BACnet Explorer") 99 | 100 | ## Example output 101 | 102 | The example output from the serial terminal of the ESP32 103 | 104 | ```txt 105 | FYI: ESP32 CAS BACnet Stack example version: 0.0.1 106 | FYI: ESP32 Chip ID: 30C6, (08A4AE30) 107 | FYI: Connecting to wifi... 108 | FYI: Connected to XXXXXXXXXX 109 | FYI: IP address: 192.168.1.66 110 | FYI: Connecting UDP resource to port=[47808] 111 | FYI: Connected to UDP port. 112 | FYI: CAS BACnet Stack version: 3.25.0 113 | FYI: BACnet device created: Device instanse=389001 114 | FYI: Enabled WriteProperty for Device 389001 115 | FYI: Enabled Read Property Multiple for Device 14 116 | FYI: Added multi-state-output (1) to Device (389001) 117 | FYI: CallbackGetPropertyUInt deviceInstance=389001, objectType=8, objectInstance=389001, propertyIdentifier=62 118 | FYI: CallbackGetPropertyUInt deviceInstance=389001, objectType=8, objectInstance=389001, propertyIdentifier=120 119 | FYI: Sent message with 25 bytes to 192.168.1.255:47808 120 | FYI: Sent broadcast IAm message 121 | FYI: FreeHeap: 270784 / 336136 (80.59 %) 122 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 123 | FYI: Recived message with 19 bytes from 192.168.1.77:47808 124 | FYI: CallbackGetPropertyUInt deviceInstance=389001, objectType=19, objectInstance=1, propertyIdentifier=74 125 | FYI: CallbackGetPropertyCharString deviceInstance=389001, objectType=19, objectInstance=1, propertyIdentifier=77 126 | FYI: CallbackGetPropertyUInt deviceInstance=389001, objectType=19, objectInstance=1, propertyIdentifier=85 127 | FYI: CallbackGetPropertyUInt deviceInstance=389001, objectType=19, objectInstance=1, propertyIdentifier=110 128 | FYI: CallbackGetPropertyCharString deviceInstance=389001, objectType=19, objectInstance=1, propertyIdentifier=110 129 | FYI: CallbackGetPropertyCharString deviceInstance=389001, objectType=19, objectInstance=1, propertyIdentifier=110 130 | FYI: CallbackGetPropertyCharString deviceInstance=389001, objectType=19, objectInstance=1, propertyIdentifier=110 131 | FYI: Sent message with 98 bytes to 192.168.1.77:47808 132 | FYI: FreeHeap: 270784 / 336136 (80.59 %) 133 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 134 | 135 | ``` 136 | 137 | ## Tested hardware 138 | 139 | - [Adafruit HUZZAH32 – ESP32 Feather Board](https://www.adafruit.com/product/3405) 140 | 141 | ## FAQ 142 | 143 | ### Can't build the project. *CASBACnetStackAdapter.h: No such file or directory* 144 | 145 | Please see this issue [CASBACnetStackAdapter.h: No such file or directory](https://github.com/chipkin/ESP32-BACnetServerExample/issues/1) 146 | -------------------------------------------------------------------------------- /docs/CASBACnetExplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chipkin/ESP32-BACnetServerExample/dfe4235e8aa6260bbd2047eb81274cbd5452ea14/docs/CASBACnetExplorer.png -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | ##################################################### 2 | 3 | Missing libary \lib\casbacnetstack\ 4 | This file is part of the CAS BACnet stack and is not included in this repo. More information about the CAS BACnet stack can be found here https://www.bacnetstack.com/ 5 | Also see this issue: https://github.com/chipkin/ESP32-BACnetServerExample/issues/1 6 | 7 | ##################################################### 8 | 9 | 10 | This directory is intended for project specific (private) libraries. 11 | PlatformIO will compile them to static libraries and link into executable file. 12 | 13 | The source code of each library should be placed in a an own separate directory 14 | ("lib/your_library_name/[here are source files]"). 15 | 16 | For example, see a structure of the following two libraries `Foo` and `Bar`: 17 | 18 | |--lib 19 | | | 20 | | |--Bar 21 | | | |--docs 22 | | | |--examples 23 | | | |--src 24 | | | |- Bar.c 25 | | | |- Bar.h 26 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 27 | | | 28 | | |--Foo 29 | | | |- Foo.c 30 | | | |- Foo.h 31 | | | 32 | | |- README --> THIS FILE 33 | | 34 | |- platformio.ini 35 | |--src 36 | |- main.c 37 | 38 | and a contents of `src/main.c`: 39 | ``` 40 | #include 41 | #include 42 | 43 | int main (void) 44 | { 45 | ... 46 | } 47 | 48 | ``` 49 | 50 | PlatformIO Library Dependency Finder will find automatically dependent 51 | libraries scanning project source files. 52 | 53 | More information about PlatformIO Library Dependency Finder 54 | - https://docs.platformio.org/page/librarymanager/ldf.html 55 | -------------------------------------------------------------------------------- /lib/cas-bacnet-stack/src/README: -------------------------------------------------------------------------------- 1 | Add source and header files here. 2 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:featheresp32] 12 | platform = espressif32 13 | board = featheresp32 14 | framework = arduino 15 | monitor_speed = 115200 -------------------------------------------------------------------------------- /src/CIBuildSettings.h: -------------------------------------------------------------------------------- 1 | // This file is overwritten in the Gitlab CI build proccess. 2 | #define CI_PIPELINE_IID 0 3 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 BACnet Server Example 3 | * -------------------------------------- 4 | * In this project we are using the CAS BACnet stack (https://www.bacnetstack.com/) to generate a 5 | * simple BACnet server with one Multi-state-value (MSV) object. The MSV shows the current mode of 6 | * the build in LED. A BACnet client (such as the CAS BACnet Explorer) can be used to write to this 7 | * MSV to change the mode. 8 | * 9 | * In this example the MSV has three possible values 10 | * - 1 = Off 11 | * - 2 = On 12 | * - 3 = Blink 13 | * 14 | * Created by: Steven Smethurst 15 | * Created on: May 8, 2019 16 | * Last updated: May 8, 2019 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | // Missing file 24 | // This file is part of the CAS BACnet stack and is not included in this repo 25 | // More information about the CAS BACnet stack can be found here https://www.bacnetstack.com/ 26 | // https://github.com/chipkin/ESP32-BACnetServerExample/issues/1 27 | #include 28 | #include 29 | 30 | // Application Version 31 | // ----------------------------- 32 | const uint32_t APPLICATION_VERSION_MAJOR = 0; 33 | const uint32_t APPLICATION_VERSION_MINOR = 0; 34 | const uint32_t APPLICATION_VERSION_PATCH = 2; 35 | 36 | // Application settings 37 | // ----------------------------- 38 | // ToDo: Update these prameters with your local wifi SSID and password. 39 | const char* APPLICATION_WIFI_SSID = "---YOUR SSID---"; 40 | const char* APPLICATION_WIFI_PASSWORD = "--YOUR PASSWORD---"; 41 | 42 | const uint32_t APPLICATION_BACNET_DEVICE_INSTANCE = 389001; 43 | const uint32_t APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE = 1; 44 | const char* APPLICATION_BACNET_OBJECT_DEVICE_OBJECT_NAME = "ESP32 BACnet Example Server"; 45 | const char* APPLICATION_BACNET_OBJECT_MSV_LED_OBJECT_NAME = "LED State"; 46 | const char* APPLICATION_BACNET_OBJECT_MSV_LED_STATE_TEXT[] = { "Off", "On", "Blink" }; 47 | const uint16_t APPLICATION_BACNET_UDP_PORT = 47808; 48 | const uint32_t APPLICATION_LED_PIN = LED_BUILTIN; 49 | const uint32_t APPLICATION_SERIAL_BAUD_RATE = 115200; 50 | const long APPLICATION_LED_BLINK_RATE_MS = 500; // interval at which to blink (milliseconds) 51 | 52 | // BACnet constants 53 | // ----------------------------- 54 | // This is a sub list of BACnet constants. A full list can be found in the documentation 55 | const uint16_t BACNET_SERVICE_READ_PROPERTY_MULTIPLE = 14; 56 | const uint16_t BACNET_SERVICE_WRITE_PROPERTY = 15; 57 | const uint16_t BACNET_OBJECT_TYPE_DEVICE = 8; 58 | const uint16_t BACNET_OBJECT_TYPE_MULTI_STATE_VALUE = 19; 59 | const uint16_t BACNET_NETWORK_TYPE_IP = 0; 60 | const uint32_t BACNET_PROPERTY_IDENTIFIER_NUMBER_OF_STATES = 74; 61 | const uint32_t BACNET_PROPERTY_IDENTIFIER_OBJECT_NAME = 77; 62 | const uint32_t BACNET_PROPERTY_IDENTIFIER_PRESENT_VALUE = 85; 63 | const uint32_t BACNET_PROPERTY_IDENTIFIER_STATE_TEXT = 110; 64 | const uint32_t BACNET_ERROR_CODE_VALUE_OUT_OF_RANGE = 37; 65 | 66 | // Wifi 67 | // create UDP instance 68 | WiFiUDP gUDP; 69 | 70 | // LED 71 | // ----------------------------- 72 | // LED Mode 73 | const uint16_t LED_MODE_OFF = 1; 74 | const uint16_t LED_MODE_ON = 2; 75 | const uint16_t LED_MODE_BLINK = 3; 76 | uint32_t gLEDMode = LED_MODE_BLINK; 77 | const uint16_t LED_MODE_STATE_COUNT = LED_MODE_BLINK; 78 | 79 | // LED State 80 | uint16_t gLEDState = LOW; 81 | 82 | // Callback functions 83 | // ----------------------------- 84 | uint16_t CallbackReceiveMessage(uint8_t* message, const uint16_t maxMessageLength, uint8_t* receivedConnectionString, const uint8_t maxConnectionStringLength, uint8_t* receivedConnectionStringLength, uint8_t* networkType); 85 | uint16_t CallbackSendMessage(const uint8_t* message, const uint16_t messageLength, const uint8_t* connectionString, const uint8_t connectionStringLength, const uint8_t networkType, bool broadcast); 86 | time_t CallbackGetSystemTime(); 87 | bool CallbackGetPropertyCharString(const uint32_t deviceInstance, const uint16_t objectType, const uint32_t objectInstance, const uint32_t propertyIdentifier, char* value, uint32_t* valueElementCount, const uint32_t maxElementCount, uint8_t* encodingType, const bool useArrayIndex, const uint32_t propertyArrayIndex); 88 | bool CallbackGetPropertyUInt(uint32_t deviceInstance, uint16_t objectType, uint32_t objectInstance, uint32_t propertyIdentifier, uint32_t* value, bool useArrayIndex, uint32_t propertyArrayIndex); 89 | bool CallbackSetPropertyUInt(const uint32_t deviceInstance, const uint16_t objectType, const uint32_t objectInstance, const uint32_t propertyIdentifier, const uint32_t value, const bool useArrayIndex, const uint32_t propertyArrayIndex, const uint8_t priority, unsigned int* errorCode); 90 | 91 | // Helpers 92 | // ----------------------------- 93 | bool GetBroadcastAddress(uint8_t* broadcastAddress, size_t maxbroadcastAddressSize); 94 | 95 | void setup() 96 | { 97 | // Hardware setup 98 | // ========================================== 99 | // Set the digital pin as output: 100 | pinMode(APPLICATION_LED_PIN, OUTPUT); 101 | Serial.begin(APPLICATION_SERIAL_BAUD_RATE); 102 | delay(3000); // Let the Serial port connect, and give us some time to reprogram if needed. 103 | 104 | Serial.print("FYI: ESP32 CAS BACnet Stack example version: "); 105 | Serial.print(APPLICATION_VERSION_MAJOR); 106 | Serial.print("."); 107 | Serial.print(APPLICATION_VERSION_MINOR); 108 | Serial.print("."); 109 | Serial.println(APPLICATION_VERSION_PATCH); 110 | Serial.print("."); 111 | Serial.println(CI_PIPELINE_IID); 112 | uint64_t chipid = ESP.getEfuseMac(); // The chip ID is essentially its MAC address(length: 6 bytes). 113 | Serial.printf("FYI: ESP32 Chip ID: %04X, (%08X)\n", (uint16_t)(chipid >> 32), (uint32_t)chipid); 114 | 115 | // WiFi connection 116 | // ========================================== 117 | Serial.println("FYI: Connecting to wifi..."); 118 | WiFi.begin(APPLICATION_WIFI_SSID, APPLICATION_WIFI_PASSWORD); 119 | 120 | // Wait for connection 121 | while (WiFi.status() != WL_CONNECTED) { 122 | delay(500); 123 | Serial.print("."); 124 | } 125 | Serial.println(""); 126 | Serial.print("FYI: Connected to "); 127 | Serial.println(APPLICATION_WIFI_SSID); 128 | Serial.print("IP address: "); 129 | Serial.println(WiFi.localIP()); 130 | Serial.print("Subnet mask: "); 131 | Serial.println(WiFi.subnetMask()); 132 | 133 | // Set up UDP port. 134 | // Connect the UDP resource to the BACnet Port 135 | Serial.printf("FYI: Connecting UDP resource to port=[%u]\n", APPLICATION_BACNET_UDP_PORT); 136 | gUDP.begin(APPLICATION_BACNET_UDP_PORT); 137 | Serial.println("FYI: Connected to UDP port."); 138 | 139 | // Set up the CAS BACnet stack. 140 | // ========================================== 141 | LoadBACnetFunctions(); 142 | Serial.print("FYI: CAS BACnet Stack version: "); 143 | Serial.print(fpGetAPIMajorVersion()); 144 | Serial.print("."); 145 | Serial.print(fpGetAPIMinorVersion()); 146 | Serial.print("."); 147 | Serial.println(fpGetAPIPatchVersion()); 148 | Serial.print("."); 149 | Serial.println(fpGetAPIBuildVersion()); 150 | 151 | // Set up CallBack functions 152 | // ------------------------------------------ 153 | // There are many call back functions that we could implement. These are the minimum required for the demo. 154 | 155 | // Message Callback Functions 156 | fpRegisterCallbackReceiveMessage(CallbackReceiveMessage); 157 | fpRegisterCallbackSendMessage(CallbackSendMessage); 158 | 159 | // System Time Callback Functions 160 | fpRegisterCallbackGetSystemTime(CallbackGetSystemTime); 161 | 162 | // Get Property Callback Functions 163 | fpRegisterCallbackGetPropertyCharacterString(CallbackGetPropertyCharString); 164 | fpRegisterCallbackGetPropertyUnsignedInteger(CallbackGetPropertyUInt); 165 | 166 | // Set Property Callback Functions 167 | fpRegisterCallbackSetPropertyUnsignedInteger(CallbackSetPropertyUInt); 168 | 169 | // Set up the BACnet device 170 | // ------------------------------------------ 171 | if (!fpAddDevice(APPLICATION_BACNET_DEVICE_INSTANCE)) { 172 | Serial.printf("Error: Could not add device. Device instanse=%u\n", APPLICATION_BACNET_DEVICE_INSTANCE); 173 | return; 174 | } 175 | Serial.printf("FYI: BACnet device created: Device instanse=%u\n", APPLICATION_BACNET_DEVICE_INSTANCE); 176 | 177 | // By default the write service is not enabled. We have to enable it to allow users to write to points. 178 | if (!fpSetServiceEnabled(APPLICATION_BACNET_DEVICE_INSTANCE, BACNET_SERVICE_WRITE_PROPERTY, true)) { 179 | Serial.printf("Error: Failed to enabled the WriteProperty service=[%u] for Device %u\n", BACNET_SERVICE_WRITE_PROPERTY, APPLICATION_BACNET_DEVICE_INSTANCE); 180 | return; 181 | } 182 | Serial.printf("FYI: Enabled WriteProperty for Device %u\n", APPLICATION_BACNET_DEVICE_INSTANCE); 183 | 184 | // Read Property Multiple service is a nice to have, not required for a BACnet server to work but it does make polling the device easier. 185 | if (!fpSetServiceEnabled(APPLICATION_BACNET_DEVICE_INSTANCE, BACNET_SERVICE_READ_PROPERTY_MULTIPLE, true)) { 186 | Serial.printf("Error: Failed to enabled the Read Property Multiple service=[%u] for Device %u\n", BACNET_SERVICE_READ_PROPERTY_MULTIPLE, APPLICATION_BACNET_DEVICE_INSTANCE); 187 | return; 188 | } 189 | Serial.printf("FYI: Enabled Read Property Multiple for Device %u\n", BACNET_SERVICE_READ_PROPERTY_MULTIPLE); 190 | 191 | // Add Objects 192 | if (!fpAddObject(APPLICATION_BACNET_DEVICE_INSTANCE, BACNET_OBJECT_TYPE_MULTI_STATE_VALUE, APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE)) { 193 | Serial.printf("Error: Failed to add multi-state-output (%u) to Device (%u)\n", APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE, APPLICATION_BACNET_DEVICE_INSTANCE); 194 | return; 195 | } 196 | fpSetPropertyWritable(APPLICATION_BACNET_DEVICE_INSTANCE, BACNET_OBJECT_TYPE_MULTI_STATE_VALUE, APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE, BACNET_PROPERTY_IDENTIFIER_PRESENT_VALUE, true); 197 | fpSetPropertyEnabled(APPLICATION_BACNET_DEVICE_INSTANCE, BACNET_OBJECT_TYPE_MULTI_STATE_VALUE, APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE, BACNET_PROPERTY_IDENTIFIER_STATE_TEXT, true); 198 | 199 | Serial.printf("FYI: Added multi-state-output (%u) to Device (%u)\n", APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE, APPLICATION_BACNET_DEVICE_INSTANCE); 200 | 201 | // Send a IAm Message to announse to the network that a new BACnet device has started. 202 | uint8_t connectionString[6]; 203 | if (!GetBroadcastAddress(connectionString, 6)) { 204 | Serial.println("Error: Could not get the broadcast IP address"); 205 | return; 206 | } 207 | connectionString[4] = APPLICATION_BACNET_UDP_PORT / 256; 208 | connectionString[5] = APPLICATION_BACNET_UDP_PORT % 256; 209 | if (!fpSendIAm(APPLICATION_BACNET_DEVICE_INSTANCE, connectionString, 6, BACNET_NETWORK_TYPE_IP, true, 65535, NULL, 0)) { 210 | Serial.printf("Error: Unable to send IAm for Device %u", APPLICATION_BACNET_DEVICE_INSTANCE); 211 | return; 212 | } 213 | Serial.println("FYI: Sent broadcast IAm message"); 214 | } 215 | 216 | void loop() 217 | { 218 | fpLoop(); 219 | unsigned long currentMillis = millis(); 220 | 221 | static unsigned long lastMemoryCheck = 0; 222 | if (lastMemoryCheck < currentMillis) { 223 | lastMemoryCheck = currentMillis + 30000; 224 | Serial.print("FYI: FreeHeap: "); 225 | Serial.print(ESP.getFreeHeap()); 226 | Serial.print(" / "); 227 | Serial.print(ESP.getHeapSize()); 228 | Serial.print(" ("); 229 | Serial.print(((float)ESP.getFreeHeap() / (float)ESP.getHeapSize()) * 100.0f); 230 | Serial.println(" %)"); 231 | } 232 | 233 | switch (gLEDMode) { 234 | default: 235 | case LED_MODE_OFF: 236 | if (gLEDState != LOW) { 237 | gLEDState = LOW; 238 | digitalWrite(APPLICATION_LED_PIN, gLEDState); 239 | } 240 | break; 241 | case LED_MODE_ON: 242 | if (gLEDState != HIGH) { 243 | gLEDState = HIGH; 244 | digitalWrite(APPLICATION_LED_PIN, gLEDState); 245 | } 246 | break; 247 | case LED_MODE_BLINK: 248 | // check to see if it's time to blink the LED; that is, if the difference 249 | // between the current time and last time you blinked the LED is bigger than 250 | // the interval at which you want to blink the LED. 251 | static long lastBlink = 0; 252 | if (currentMillis - lastBlink >= APPLICATION_LED_BLINK_RATE_MS) { 253 | // save the last time you blinked the LED 254 | lastBlink = currentMillis; 255 | 256 | // if the LED is off turn it on and vice-versa: 257 | if (gLEDState == LOW) { 258 | gLEDState = HIGH; 259 | Serial.print("+"); 260 | } else { 261 | gLEDState = LOW; 262 | Serial.print("-"); 263 | } 264 | 265 | // set the LED with the ledState of the variable: 266 | digitalWrite(APPLICATION_LED_PIN, gLEDState); 267 | } 268 | break; 269 | } 270 | } 271 | 272 | // Helper 273 | // --------------------------------------------------------------------------- 274 | bool GetBroadcastAddress(uint8_t* broadcastAddress, size_t maxbroadcastAddressSize) 275 | { 276 | if (broadcastAddress == NULL || maxbroadcastAddressSize < 4) { 277 | return false; // Not enugh space. 278 | } 279 | IPAddress localIP = WiFi.localIP(); 280 | IPAddress subnetMask = WiFi.subnetMask(); 281 | 282 | broadcastAddress[0] = localIP[0] | ~subnetMask[0]; 283 | broadcastAddress[1] = localIP[1] | ~subnetMask[1]; 284 | broadcastAddress[2] = localIP[2] | ~subnetMask[2]; 285 | broadcastAddress[3] = localIP[3] | ~subnetMask[3]; 286 | return true; 287 | } 288 | 289 | // Callback used by the BACnet Stack to check if there is a message to process 290 | // --------------------------------------------------------------------------- 291 | 292 | uint16_t CallbackReceiveMessage(uint8_t* message, const uint16_t maxMessageLength, uint8_t* receivedConnectionString, const uint8_t maxConnectionStringLength, uint8_t* receivedConnectionStringLength, uint8_t* networkType) 293 | { 294 | // Check parameters 295 | if (message == NULL || maxMessageLength == 0) { 296 | Serial.println("Error: Invalid input buffer"); 297 | return 0; 298 | } 299 | if (receivedConnectionString == NULL || maxConnectionStringLength == 0) { 300 | Serial.println("Error: Invalid connection string buffer"); 301 | return 0; 302 | } 303 | if (maxConnectionStringLength < 6) { 304 | Serial.println("Error: Not enough space for a UDP connection string"); 305 | return 0; 306 | } 307 | 308 | // processing incoming packet, must be called before reading the buffer 309 | gUDP.parsePacket(); 310 | 311 | int32_t bytesRead = gUDP.read(message, maxMessageLength); 312 | if (bytesRead <= 0) { 313 | return 0; 314 | } 315 | 316 | // We got a message. 317 | IPAddress remoteIP = gUDP.remoteIP(); 318 | receivedConnectionString[0] = remoteIP[0]; 319 | receivedConnectionString[1] = remoteIP[1]; 320 | receivedConnectionString[2] = remoteIP[2]; 321 | receivedConnectionString[3] = remoteIP[3]; 322 | 323 | uint16_t remotePort = gUDP.remotePort(); 324 | receivedConnectionString[4] = remotePort / 256; 325 | receivedConnectionString[5] = remotePort % 256; 326 | 327 | *receivedConnectionStringLength = 6; 328 | *networkType = BACNET_NETWORK_TYPE_IP; 329 | 330 | Serial.printf("FYI: Recived message with %u bytes from %u.%u.%u.%u:%u\n", bytesRead, receivedConnectionString[0], receivedConnectionString[1], receivedConnectionString[2], receivedConnectionString[3], remotePort); 331 | // Serial.printf("Message first 10 bytes: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X \n", message[0], message[1], message[2], message[3], message[4], message[5], message[6], message[7], message[8], message[9]); 332 | return bytesRead; 333 | } 334 | uint16_t CallbackSendMessage(const uint8_t* message, const uint16_t messageLength, const uint8_t* connectionString, const uint8_t connectionStringLength, const uint8_t networkType, bool broadcast) 335 | { 336 | if (message == NULL || messageLength == 0) { 337 | Serial.println("FYI: Nothing to send"); 338 | return 0; 339 | } 340 | if (connectionString == NULL || connectionStringLength == 0) { 341 | Serial.println("FYI: No connection string"); 342 | return 0; 343 | } 344 | 345 | // Verify Network Type 346 | if (networkType != BACNET_NETWORK_TYPE_IP) { 347 | Serial.println("FYI: Message for different network"); 348 | return 0; 349 | } 350 | 351 | // Prepare the IP Address 352 | char destiationIPAddressAsString[32]; 353 | if (broadcast) { 354 | uint8_t broadcastIPAddress[4]; 355 | if (!GetBroadcastAddress(broadcastIPAddress, 4)) { 356 | Serial.println("Error: Could not get the broadcast iIP address"); 357 | return 0; 358 | } 359 | snprintf(destiationIPAddressAsString, 32, "%u.%u.%u.%u", broadcastIPAddress[0], broadcastIPAddress[1], broadcastIPAddress[2], broadcastIPAddress[3]); 360 | } else { 361 | snprintf(destiationIPAddressAsString, 32, "%u.%u.%u.%u", connectionString[0], connectionString[1], connectionString[2], connectionString[3]); 362 | } 363 | 364 | // Get the port 365 | uint16_t udpPort = 0; 366 | udpPort += connectionString[4] * 256; 367 | udpPort += connectionString[5]; 368 | 369 | // Send 370 | gUDP.beginPacket(destiationIPAddressAsString, udpPort); 371 | gUDP.write(message, messageLength); 372 | gUDP.endPacket(); 373 | 374 | Serial.printf("FYI: Sent message with %u bytes to %s:%u\n", messageLength, destiationIPAddressAsString, udpPort); 375 | // Serial.printf("Message first 10 bytes: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X \n", message[0], message[1], message[2], message[3], message[4], message[5], message[6], message[7], message[8], message[9]); 376 | return messageLength; 377 | } 378 | time_t CallbackGetSystemTime() 379 | { 380 | return millis() / 1000; 381 | } 382 | bool CallbackGetPropertyCharString(const uint32_t deviceInstance, const uint16_t objectType, const uint32_t objectInstance, const uint32_t propertyIdentifier, char* value, uint32_t* valueElementCount, const uint32_t maxElementCount, uint8_t* encodingType, const bool useArrayIndex, const uint32_t propertyArrayIndex) 383 | { 384 | Serial.printf("FYI: CallbackGetPropertyCharString deviceInstance=%d, objectType=%d, objectInstance=%d, propertyIdentifier=%d\n", deviceInstance, objectType, objectInstance, propertyIdentifier); 385 | 386 | if (deviceInstance != APPLICATION_BACNET_DEVICE_INSTANCE) { 387 | return false; 388 | } 389 | 390 | if (objectType == BACNET_OBJECT_TYPE_MULTI_STATE_VALUE && objectInstance == APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE) { 391 | if (propertyIdentifier == BACNET_PROPERTY_IDENTIFIER_OBJECT_NAME && useArrayIndex == false) { 392 | size_t objectNameStringLength = strlen(APPLICATION_BACNET_OBJECT_MSV_LED_OBJECT_NAME); 393 | if (objectNameStringLength <= maxElementCount) { 394 | memcpy(value, APPLICATION_BACNET_OBJECT_MSV_LED_OBJECT_NAME, objectNameStringLength); 395 | *valueElementCount = objectNameStringLength; 396 | return true; 397 | } 398 | } else if (propertyIdentifier == BACNET_PROPERTY_IDENTIFIER_STATE_TEXT && useArrayIndex == true) { 399 | if (propertyArrayIndex > LED_MODE_STATE_COUNT || propertyArrayIndex <= 0) { 400 | return false; // Invalid range 401 | } 402 | 403 | size_t objectNameStringLength = strlen(APPLICATION_BACNET_OBJECT_MSV_LED_STATE_TEXT[propertyArrayIndex - 1]); 404 | if (objectNameStringLength <= maxElementCount) { 405 | memcpy(value, APPLICATION_BACNET_OBJECT_MSV_LED_STATE_TEXT[propertyArrayIndex - 1], objectNameStringLength); 406 | *valueElementCount = objectNameStringLength; 407 | return true; 408 | } 409 | } 410 | } else if (objectType == BACNET_OBJECT_TYPE_DEVICE && objectInstance == APPLICATION_BACNET_DEVICE_INSTANCE && propertyIdentifier == BACNET_PROPERTY_IDENTIFIER_OBJECT_NAME && useArrayIndex == false) { 411 | size_t objectNameStringLength = strlen(APPLICATION_BACNET_OBJECT_DEVICE_OBJECT_NAME); 412 | if (objectNameStringLength <= maxElementCount) { 413 | memcpy(value, APPLICATION_BACNET_OBJECT_DEVICE_OBJECT_NAME, objectNameStringLength); 414 | *valueElementCount = objectNameStringLength; 415 | return true; 416 | } 417 | } 418 | 419 | return false; 420 | } 421 | 422 | bool CallbackGetPropertyUInt(uint32_t deviceInstance, uint16_t objectType, uint32_t objectInstance, uint32_t propertyIdentifier, uint32_t* value, bool useArrayIndex, uint32_t propertyArrayIndex) 423 | { 424 | Serial.printf("FYI: CallbackGetPropertyUInt deviceInstance=%d, objectType=%d, objectInstance=%d, propertyIdentifier=%d\n", deviceInstance, objectType, objectInstance, propertyIdentifier); 425 | 426 | if (deviceInstance == APPLICATION_BACNET_DEVICE_INSTANCE && objectType == BACNET_OBJECT_TYPE_MULTI_STATE_VALUE && objectInstance == APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE) { 427 | if (propertyIdentifier == BACNET_PROPERTY_IDENTIFIER_PRESENT_VALUE && useArrayIndex == false) { 428 | *value = gLEDMode; 429 | return true; 430 | } else if (propertyIdentifier == BACNET_PROPERTY_IDENTIFIER_NUMBER_OF_STATES && useArrayIndex == false) { 431 | *value = LED_MODE_STATE_COUNT; 432 | return true; 433 | } else if (propertyIdentifier == BACNET_PROPERTY_IDENTIFIER_STATE_TEXT && useArrayIndex == true && propertyArrayIndex == 0) { 434 | *value = LED_MODE_STATE_COUNT; 435 | return true; 436 | } 437 | } 438 | 439 | return false; 440 | } 441 | bool CallbackSetPropertyUInt(const uint32_t deviceInstance, const uint16_t objectType, const uint32_t objectInstance, const uint32_t propertyIdentifier, const uint32_t value, const bool useArrayIndex, const uint32_t propertyArrayIndex, const uint8_t priority, unsigned int* errorCode) 442 | { 443 | Serial.printf("FYI: CallbackSetPropertyUInt deviceInstance=%d, objectType=%d, objectInstance=%d, propertyIdentifier=%d, value=%d\n", deviceInstance, objectType, objectInstance, propertyIdentifier, value); 444 | 445 | if (deviceInstance == APPLICATION_BACNET_DEVICE_INSTANCE && objectType == BACNET_OBJECT_TYPE_MULTI_STATE_VALUE && objectInstance == APPLICATION_BACNET_OBJECT_MSV_LED_INSTANCE && propertyIdentifier == BACNET_PROPERTY_IDENTIFIER_PRESENT_VALUE && useArrayIndex == false) { 446 | switch (value) { 447 | case LED_MODE_OFF: 448 | case LED_MODE_ON: 449 | case LED_MODE_BLINK: 450 | gLEDMode = value; 451 | return true; 452 | default: 453 | // Out of range 454 | *errorCode = BACNET_ERROR_CODE_VALUE_OUT_OF_RANGE; 455 | return false; 456 | } 457 | } 458 | 459 | return false; 460 | } -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------