esp32: replace port with PlatformIO implementation and add CI build (#1292)
This commit is contained in:
@@ -328,3 +328,15 @@ jobs:
|
||||
gcc --version
|
||||
make clean
|
||||
make gtk-discover
|
||||
|
||||
ports-esp32:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
pip3 install platformio
|
||||
- name: Build ESP32 Port
|
||||
run: |
|
||||
pio run -d ports/esp32
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
@@ -0,0 +1,132 @@
|
||||
# ESP32 Port
|
||||
|
||||
Date: 2026-04-04
|
||||
Author: Kato Gangstad
|
||||
|
||||
This folder contains the ESP32 BACnet port built with PlatformIO.
|
||||
It targets multiple ESP32-family boards and covers BACnet MS/TP, BACnet/IP, and BACnet Gateway (based on apps/gateway2) profiles.
|
||||
It is based on the current lwIP port and Pico port patterns in this repository.
|
||||
|
||||
## Current Scope
|
||||
|
||||
- BACnet MS/TP transport for ESP32 boards with RS485
|
||||
- BACnet/IP transport for compact WiFi and Ethernet ESP32 targets
|
||||
- BACnet routing between BACnet/IP and BACnet MS/TP
|
||||
|
||||
## Port Identity
|
||||
|
||||
It is the ESP32 BACnet port for the current PlatformIO-based targets in this repository.
|
||||
|
||||
## Supported Board Profiles
|
||||
|
||||
The current board coverage includes:
|
||||
|
||||
- Seeed Studio XIAO ESP32C3 for ultra-compact BACnet/IP
|
||||
- M5StamPLC for BACnet MS/TP and BACnet router builds
|
||||
- Olimex ESP32-POE for BACnet/IP
|
||||
|
||||
The upstream BACnet stack has a broad source set and multiple app profiles.
|
||||
This port selects the ESP32-specific transport, board, and application wiring needed for the environments documented below.
|
||||
|
||||
## Files
|
||||
|
||||
- src/main.cpp: Application loop, BACnet object table, and PLC I/O sync
|
||||
- src/mstimer_init.c: millisecond timer hook used by mstimer
|
||||
- src/rs485.c: ESP32 RS485 low-level driver for MS/TP
|
||||
- src/dlenv.c: MS/TP datalink environment setup and port wiring
|
||||
- src/bip_socket.cpp: BACnet/IP UDP socket bridge for WiFi and Ethernet targets
|
||||
- extra_script.py: Injects BACnet core/basic sources into the PlatformIO build
|
||||
|
||||
## Build Notes
|
||||
|
||||
1. Open this folder as a PlatformIO project root, or run PlatformIO with this folder as cwd.
|
||||
2. Start with the environment that matches your board and transport profile.
|
||||
3. The `platformio.ini` file defines all tested board environments for this port.
|
||||
4. Ensure PlatformIO CLI is available in your shell (`pio --version`).
|
||||
5. Build with `pio run -e <environment-name>`.
|
||||
|
||||
## Tested Environments
|
||||
|
||||
The following PlatformIO environments have been built and uploaded successfully:
|
||||
|
||||
- `xiao-esp32c3-wifi-bip` on Seeed Studio XIAO ESP32C3
|
||||
- `m5stamplc-mstp` on M5StamPLC
|
||||
- `m5stamplc-gateway-bip-mstp` on M5StamPLC
|
||||
- `esp32-poe-wifi-bip` on Olimex ESP32-POE
|
||||
|
||||
## Validation Tools
|
||||
|
||||
Interoperability and functional verification have been tested with:
|
||||
|
||||
- YABE
|
||||
- Honeywell Eaglehawk 4.15
|
||||
- Tridium Niagara 4.15
|
||||
|
||||
## Board Pictures
|
||||
|
||||
The following board photos document the hardware used for the tested environments.
|
||||
|
||||
### Seeed Studio XIAO ESP32C3
|
||||
|
||||
Used for:
|
||||
|
||||
- `xiao-esp32c3-wifi-bip`
|
||||
|
||||
Why this board stands out:
|
||||
|
||||
- Extremely compact form factor for a BACnet/IP target
|
||||
- Physical size: 21 x 17.8 mm
|
||||
- Strong candidate for one of the smallest BACnet/IP device ever !
|
||||
|
||||
Hardware summary:
|
||||
|
||||
- MCU: ESP32-C3
|
||||
- CPU type: 32-bit single-core RISC-V
|
||||
- CPU clock: 160 MHz
|
||||
- RAM: 320 KB
|
||||
- Flash: 4 MB
|
||||
|
||||

|
||||
|
||||
### M5StamPLC
|
||||
|
||||
- BACnet server object model with Device + Binary Input + Binary Output objects
|
||||
- Runtime mapping:
|
||||
- BI 0..7 <- PLC inputs 1..8
|
||||
- BO 0..3 -> PLC relays 1..4
|
||||
- RS485 pin mapping aligned with the M5StamPLC hardware profile:
|
||||
- TX: GPIO0
|
||||
- RX: GPIO39
|
||||
- DIR: GPIO46
|
||||
|
||||
Used for:
|
||||
|
||||
- `m5stamplc-mstp`
|
||||
- `m5stamplc-bip`
|
||||
- `m5stamplc-gateway-bip-mstp`
|
||||
- soon to come `m5stamplc-poe-bip`
|
||||
|
||||
Hardware summary:
|
||||
|
||||
- MCU: ESP32-S3
|
||||
- CPU clock: 240 MHz
|
||||
- RAM: 320 KB
|
||||
- Flash: 8 MB
|
||||
|
||||

|
||||
|
||||
### Olimex ESP32-POE
|
||||
|
||||
Used for:
|
||||
|
||||
- `esp32-poe-wifi-bip`
|
||||
- `esp32-poe-eth-bip`
|
||||
|
||||
Hardware summary:
|
||||
|
||||
- MCU: ESP32
|
||||
- CPU clock: 240 MHz
|
||||
- RAM: 320 KB
|
||||
- Flash: 4 MB
|
||||
|
||||

|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "M5Stack StamPLC",
|
||||
"url": "https://m5stack.com/products/m5stamplc",
|
||||
"vendor": "M5Stack",
|
||||
"upload": {
|
||||
"speed": 460800,
|
||||
"maximum_size": 8388608,
|
||||
"maximum_ram_size": 327680
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 372 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,120 @@
|
||||
# @file
|
||||
# @brief Build-time BACnet source wiring for PlatformIO environments
|
||||
# @author Kato Gangstad
|
||||
|
||||
Import("env")
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
PIOENV = env.get("PIOENV", "")
|
||||
if PIOENV not in (
|
||||
"m5stamplc-mstp",
|
||||
"m5stamplc-bip",
|
||||
"esp32-poe-wifi-bip",
|
||||
"esp32-poe-eth-bip",
|
||||
"xiao-esp32c3-wifi-bip",
|
||||
"m5stamplc-gateway-bip-mstp",
|
||||
):
|
||||
Return()
|
||||
|
||||
project_dir = Path(env.subst("$PROJECT_DIR")).resolve()
|
||||
bacnet_src_root = (project_dir / ".." / ".." / "src").resolve()
|
||||
|
||||
bacnet_common_sources = [
|
||||
"bacnet/basic/binding/address.c",
|
||||
"bacnet/basic/server/bacnet_device.c",
|
||||
"bacnet/basic/object/ai.c",
|
||||
"bacnet/basic/object/bi.c",
|
||||
"bacnet/basic/object/bo.c",
|
||||
"bacnet/basic/npdu/h_npdu.c",
|
||||
"bacnet/basic/service/h_apdu.c",
|
||||
"bacnet/basic/service/h_dcc.c",
|
||||
"bacnet/basic/service/h_noserv.c",
|
||||
"bacnet/basic/service/h_rd.c",
|
||||
"bacnet/basic/service/h_rp.c",
|
||||
"bacnet/basic/service/h_rpm.c",
|
||||
"bacnet/basic/service/h_whohas.c",
|
||||
"bacnet/basic/service/h_whois.c",
|
||||
"bacnet/basic/service/h_wp.c",
|
||||
"bacnet/basic/service/s_iam.c",
|
||||
"bacnet/basic/service/s_ihave.c",
|
||||
"bacnet/basic/sys/datetime_mstimer.c",
|
||||
"bacnet/basic/sys/days.c",
|
||||
"bacnet/basic/sys/dst.c",
|
||||
"bacnet/basic/sys/debug.c",
|
||||
"bacnet/basic/sys/ringbuf.c",
|
||||
"bacnet/basic/sys/fifo.c",
|
||||
"bacnet/basic/sys/mstimer.c",
|
||||
"bacnet/basic/tsm/tsm.c",
|
||||
"bacnet/abort.c",
|
||||
"bacnet/bacaction.c",
|
||||
"bacnet/bacaddr.c",
|
||||
"bacnet/bacapp.c",
|
||||
"bacnet/bacdcode.c",
|
||||
"bacnet/bacdest.c",
|
||||
"bacnet/bacdevobjpropref.c",
|
||||
"bacnet/bacerror.c",
|
||||
"bacnet/bacint.c",
|
||||
"bacnet/bacreal.c",
|
||||
"bacnet/bacstr.c",
|
||||
"bacnet/datetime.c",
|
||||
"bacnet/dcc.c",
|
||||
"bacnet/hostnport.c",
|
||||
"bacnet/iam.c",
|
||||
"bacnet/ihave.c",
|
||||
"bacnet/indtext.c",
|
||||
"bacnet/memcopy.c",
|
||||
"bacnet/npdu.c",
|
||||
"bacnet/proplist.c",
|
||||
"bacnet/rd.c",
|
||||
"bacnet/reject.c",
|
||||
"bacnet/rp.c",
|
||||
"bacnet/rpm.c",
|
||||
"bacnet/timestamp.c",
|
||||
"bacnet/whohas.c",
|
||||
"bacnet/whois.c",
|
||||
"bacnet/wp.c",
|
||||
"bacnet/cov.c",
|
||||
"bacnet/basic/sys/keylist.c",
|
||||
]
|
||||
|
||||
bacnet_transport_sources = {
|
||||
"m5stamplc-mstp": [
|
||||
"bacnet/datalink/cobs.c",
|
||||
"bacnet/datalink/crc.c",
|
||||
"bacnet/datalink/dlmstp.c",
|
||||
"bacnet/datalink/mstp.c",
|
||||
"bacnet/datalink/mstptext.c",
|
||||
],
|
||||
"m5stamplc-bip": [
|
||||
"bacnet/datalink/bvlc.c",
|
||||
],
|
||||
"esp32-poe-wifi-bip": [
|
||||
"bacnet/datalink/bvlc.c",
|
||||
],
|
||||
"esp32-poe-eth-bip": [
|
||||
"bacnet/datalink/bvlc.c",
|
||||
],
|
||||
"xiao-esp32c3-wifi-bip": [
|
||||
"bacnet/datalink/bvlc.c",
|
||||
],
|
||||
"m5stamplc-gateway-bip-mstp": [
|
||||
"bacnet/datalink/bvlc.c",
|
||||
"bacnet/datalink/cobs.c",
|
||||
"bacnet/datalink/crc.c",
|
||||
"bacnet/datalink/dlmstp.c",
|
||||
"bacnet/datalink/mstp.c",
|
||||
"bacnet/datalink/mstptext.c",
|
||||
],
|
||||
}
|
||||
|
||||
bacnet_sources = bacnet_common_sources + bacnet_transport_sources.get(PIOENV, [])
|
||||
|
||||
env.Append(CPPPATH=[str(project_dir / "src"), str(bacnet_src_root)])
|
||||
|
||||
for rel in bacnet_sources:
|
||||
env.BuildSources(
|
||||
str(Path("$BUILD_DIR") / "bacnet"),
|
||||
str(bacnet_src_root),
|
||||
src_filter=f"+<{rel}>",
|
||||
)
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
This directory is intended for the project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link to executable file.
|
||||
|
||||
The source code of each library should be placed in separate directory, like
|
||||
"lib/private_lib/[here are source files]".
|
||||
|
||||
For example, see how can be organized `Foo` and `Bar` libraries:
|
||||
|
||||
|--lib
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |- readme.txt --> THIS FILE
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Then in `src/main.c` you should use:
|
||||
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
// rest H/C/CPP code
|
||||
|
||||
PlatformIO will find your libraries automatically, configure preprocessor's
|
||||
include paths and build them.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- http://docs.platformio.org/page/librarymanager/ldf.html
|
||||
+148
-7
@@ -6,14 +6,155 @@
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; http://docs.platformio.org/page/projectconf.html
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32thing]
|
||||
[platformio]
|
||||
default_envs = m5stamplc-mstp
|
||||
|
||||
[wifi_common]
|
||||
build_flags =
|
||||
-DWIFI_SSID=\"REPLACE_WITH_WIFI_SSID\"
|
||||
-DWIFI_PASS=\"REPLACE_WITH_WIFI_PASSWORD\"
|
||||
|
||||
[bip_common]
|
||||
build_flags =
|
||||
-UBACDL_MSTP
|
||||
-DBACDL_BIP
|
||||
-DBACNET_IP_PORT=47808
|
||||
-DMAX_APDU=1476
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
board = esp32thing
|
||||
framework = espidf
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
upload_speed = 921600
|
||||
extra_scripts = pre:extra_script.py
|
||||
lib_deps =
|
||||
m5stack/M5StamPLC@^1.2.0
|
||||
build_unflags =
|
||||
-std=gnu++11
|
||||
build_flags =
|
||||
-std=gnu11
|
||||
-std=gnu++17
|
||||
-I src
|
||||
-I ../../src
|
||||
-DBACDL_MSTP
|
||||
-DMAX_TSM_TRANSACTIONS=1
|
||||
-DPRINT_ENABLED=0
|
||||
-DBACNET_BIG_ENDIAN=0
|
||||
-DCRC_USE_TABLE
|
||||
-DBACAPP_MINIMAL
|
||||
-DBACAPP_TIMESTAMP
|
||||
-DBACNET_STACK_DEPRECATED_DISABLE
|
||||
-DBACNET_PROTOCOL_REVISION=16
|
||||
-DCONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT
|
||||
-DCONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT
|
||||
-DCONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT
|
||||
|
||||
upload_port = COM7
|
||||
upload_speed = 1152000
|
||||
[env:m5stamplc-mstp]
|
||||
board = m5stamplc
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-DMAX_APDU=480
|
||||
build_src_filter =
|
||||
+<main.cpp>
|
||||
+<bacnet_app.c>
|
||||
+<mstimer_init.c>
|
||||
+<rs485.c>
|
||||
+<dlenv.c>
|
||||
lib_deps = m5stack/M5StamPLC@^1.2.0
|
||||
|
||||
monitor_baud = 115200
|
||||
[env:m5stamplc-bip]
|
||||
board = m5stamplc
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
${bip_common.build_flags}
|
||||
${wifi_common.build_flags}
|
||||
-DUSE_M5STAMPLC_IO=1
|
||||
build_src_filter =
|
||||
+<main_bip.cpp>
|
||||
+<bacnet_app.c>
|
||||
+<bip.c>
|
||||
+<bip_init.c>
|
||||
+<bvlc.c>
|
||||
+<bip_socket.cpp>
|
||||
+<mstimer_init.c>
|
||||
lib_deps = m5stack/M5StamPLC@^1.2.0
|
||||
|
||||
[env:esp32-poe-wifi-bip]
|
||||
board = esp32-poe
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
${bip_common.build_flags}
|
||||
${wifi_common.build_flags}
|
||||
-DUSE_M5STAMPLC_IO=0
|
||||
-DUSE_ETH_INTERFACE=0
|
||||
build_src_filter =
|
||||
+<main_bip.cpp>
|
||||
+<bacnet_app.c>
|
||||
+<bip.c>
|
||||
+<bip_init.c>
|
||||
+<bvlc.c>
|
||||
+<bip_socket.cpp>
|
||||
+<mstimer_init.c>
|
||||
lib_deps =
|
||||
|
||||
[env:esp32-poe-eth-bip]
|
||||
board = esp32-poe
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
${bip_common.build_flags}
|
||||
-DUSE_M5STAMPLC_IO=0
|
||||
-DUSE_ETH_INTERFACE=1
|
||||
build_src_filter =
|
||||
+<main_bip.cpp>
|
||||
+<bacnet_app.c>
|
||||
+<bip.c>
|
||||
+<bip_init.c>
|
||||
+<bvlc.c>
|
||||
+<bip_socket.cpp>
|
||||
+<mstimer_init.c>
|
||||
lib_deps =
|
||||
|
||||
[env:xiao-esp32c3-wifi-bip]
|
||||
board = seeed_xiao_esp32c3
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
${bip_common.build_flags}
|
||||
${wifi_common.build_flags}
|
||||
-DUSE_M5STAMPLC_IO=0
|
||||
-DUSE_ETH_INTERFACE=0
|
||||
build_src_filter =
|
||||
+<main_bip.cpp>
|
||||
+<bacnet_app.c>
|
||||
+<bip.c>
|
||||
+<bip_init.c>
|
||||
+<bvlc.c>
|
||||
+<bip_socket.cpp>
|
||||
+<mstimer_init.c>
|
||||
lib_deps =
|
||||
|
||||
[env:m5stamplc-gateway-bip-mstp]
|
||||
board = m5stamplc
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
${wifi_common.build_flags}
|
||||
-DMAX_APDU=480
|
||||
-UBACDL_MSTP
|
||||
-DBACDL_MSTP
|
||||
-DBACDL_BIP
|
||||
-DBACNET_IP_PORT=47808
|
||||
-DROUTER_BIP_NET=1
|
||||
-DROUTER_MSTP_NET=200
|
||||
-DROUTER_MSTP_MAC=2
|
||||
build_src_filter =
|
||||
+<main_gateway_router.cpp>
|
||||
+<bacnet_app.c>
|
||||
+<bip.c>
|
||||
+<bip_init.c>
|
||||
+<bvlc.c>
|
||||
+<bip_socket.cpp>
|
||||
+<mstimer_init.c>
|
||||
+<rs485.c>
|
||||
+<dlenv.c>
|
||||
lib_deps = m5stack/M5StamPLC@^1.2.0
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
Bacnet Server for Espressif ESP32
|
||||
Steve Karg Bacnet stack using PlatformIO open source ecosystem for IoT development on VSCode or Atom
|
||||
F. Chaxel 2017
|
||||
|
||||
TODO list :
|
||||
|
||||
(Install VSCode or Atom and add the PlatformIO extension)
|
||||
|
||||
Edit platformio.ini to adjust board, Com Port, ...
|
||||
|
||||
Goto lib/stack and copy the requested files from Steve code :
|
||||
|
||||
all .h from include directory (not all required by it's simple)
|
||||
|
||||
these .c files from src or demo/handlers
|
||||
abort.c
|
||||
address.c
|
||||
apdu.c
|
||||
bacaddr.c
|
||||
bacapp.c
|
||||
bacdcode.c
|
||||
bacerror.c
|
||||
bacint.c
|
||||
bacreal.c
|
||||
bacstr.c
|
||||
bip.c
|
||||
bvlc.c
|
||||
cov.c
|
||||
datetime.c
|
||||
bacdevobjpropref.c
|
||||
dcc.c
|
||||
debug.c
|
||||
h_cov.c
|
||||
h_ucov.c
|
||||
h_npdu.c
|
||||
h_rp.c
|
||||
h_rpm.c
|
||||
h_whois.c
|
||||
h_wp.c
|
||||
iam.c
|
||||
hostnport.c
|
||||
lighting.c
|
||||
memcopy.c
|
||||
noserv.c
|
||||
npdu.c
|
||||
proplist.c
|
||||
reject.c
|
||||
rp.c
|
||||
rpm.c
|
||||
s_iam.c
|
||||
tsm.c
|
||||
whois.c
|
||||
wp.c
|
||||
|
||||
Modify
|
||||
in config.h
|
||||
MAX_TSM_TRANSACTIONS 255, set the value to 10 for instances
|
||||
in main.c
|
||||
wifi_config to fit your wifi network
|
||||
BACNET_LED 5, set another IO number depending of your board
|
||||
|
||||
A lot of Warning will be issued at compile time due to the redefinition of BIT macros.
|
||||
Could be removes by placing a #ifndef #BIT0 .. #endif arround the BIT macro in bits.h,
|
||||
and moving to the top of include list
|
||||
#include "bacnet/datalink/datalink.h" in tsm.c, s_iam and in device.c
|
||||
#include "bacport.h" in bip.c and in bip.h (redondant include in bip.c)
|
||||
#include "bacnet/datalink/bvlc.h" in bvlc.c
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# Disable formatting for now as there is external code. We should move external
|
||||
# code to a separate directory and enable formatting for our code.
|
||||
DisableFormat: true
|
||||
|
||||
# DisableFormat will not disable include sorting with some versions.
|
||||
SortIncludes: Never
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,151 +0,0 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
|
||||
* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*********************************************************************/
|
||||
#ifndef AI_H
|
||||
#define AI_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/rp.h"
|
||||
#include "bacnet/wp.h"
|
||||
#if defined(INTRINSIC_REPORTING)
|
||||
#include "bacnet/basic/object/nc.h"
|
||||
#include "bacnet/getevent.h"
|
||||
#include "bacnet/alarm_ack.h"
|
||||
#include "bacnet/get_alarm_sum.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
typedef struct analog_input_descr {
|
||||
unsigned Event_State:3;
|
||||
float Present_Value;
|
||||
BACNET_RELIABILITY Reliability;
|
||||
bool Out_Of_Service;
|
||||
uint8_t Units;
|
||||
float Prior_Value;
|
||||
float COV_Increment;
|
||||
bool Changed;
|
||||
#if defined(INTRINSIC_REPORTING)
|
||||
uint32_t Time_Delay;
|
||||
uint32_t Notification_Class;
|
||||
float High_Limit;
|
||||
float Low_Limit;
|
||||
float Deadband;
|
||||
unsigned Limit_Enable:2;
|
||||
unsigned Event_Enable:3;
|
||||
unsigned Notify_Type:1;
|
||||
ACKED_INFO Acked_Transitions[MAX_BACNET_EVENT_TRANSITION];
|
||||
BACNET_DATE_TIME Event_Time_Stamps[MAX_BACNET_EVENT_TRANSITION];
|
||||
/* time to generate event notification */
|
||||
uint32_t Remaining_Time_Delay;
|
||||
/* AckNotification informations */
|
||||
ACK_NOTIFICATION Ack_notify_data;
|
||||
#endif
|
||||
} ANALOG_INPUT_DESCR;
|
||||
|
||||
void Analog_Input_Property_Lists(
|
||||
const int32_t **pRequired,
|
||||
const int32_t **pOptional,
|
||||
const int32_t **pProprietary);
|
||||
|
||||
bool Analog_Input_Valid_Instance(
|
||||
uint32_t object_instance);
|
||||
unsigned Analog_Input_Count(
|
||||
void);
|
||||
uint32_t Analog_Input_Index_To_Instance(
|
||||
unsigned index);
|
||||
unsigned Analog_Input_Instance_To_Index(
|
||||
uint32_t instance);
|
||||
bool Analog_Input_Object_Instance_Add(
|
||||
uint32_t instance);
|
||||
|
||||
bool Analog_Input_Object_Name(
|
||||
uint32_t object_instance,
|
||||
BACNET_CHARACTER_STRING * object_name);
|
||||
bool Analog_Input_Name_Set(
|
||||
uint32_t object_instance,
|
||||
const char *new_name);
|
||||
|
||||
const char *Analog_Input_Description(
|
||||
uint32_t instance);
|
||||
bool Analog_Input_Description_Set(
|
||||
uint32_t instance,
|
||||
const char *new_name);
|
||||
|
||||
bool Analog_Input_Units_Set(
|
||||
uint32_t instance,
|
||||
uint16_t units);
|
||||
uint16_t Analog_Input_Units(
|
||||
uint32_t instance);
|
||||
|
||||
int Analog_Input_Read_Property(
|
||||
BACNET_READ_PROPERTY_DATA * rpdata);
|
||||
bool Analog_Input_Write_Property(
|
||||
BACNET_WRITE_PROPERTY_DATA * wp_data);
|
||||
|
||||
float Analog_Input_Present_Value(
|
||||
uint32_t object_instance);
|
||||
void Analog_Input_Present_Value_Set(
|
||||
uint32_t object_instance,
|
||||
float value);
|
||||
|
||||
bool Analog_Input_Out_Of_Service(
|
||||
uint32_t object_instance);
|
||||
void Analog_Input_Out_Of_Service_Set(
|
||||
uint32_t object_instance,
|
||||
bool oos_flag);
|
||||
|
||||
bool Analog_Input_Change_Of_Value(
|
||||
uint32_t instance);
|
||||
void Analog_Input_Change_Of_Value_Clear(
|
||||
uint32_t instance);
|
||||
bool Analog_Input_Encode_Value_List(
|
||||
uint32_t object_instance,
|
||||
BACNET_PROPERTY_VALUE * value_list);
|
||||
float Analog_Input_COV_Increment(
|
||||
uint32_t instance);
|
||||
void Analog_Input_COV_Increment_Set(
|
||||
uint32_t instance,
|
||||
float value);
|
||||
|
||||
/* note: header of Intrinsic_Reporting function is required
|
||||
even when INTRINSIC_REPORTING is not defined */
|
||||
void Analog_Input_Intrinsic_Reporting(
|
||||
uint32_t object_instance);
|
||||
|
||||
#if defined(INTRINSIC_REPORTING)
|
||||
int Analog_Input_Event_Information(
|
||||
unsigned index,
|
||||
BACNET_GET_EVENT_INFORMATION_DATA * getevent_data);
|
||||
|
||||
int Analog_Input_Alarm_Ack(
|
||||
BACNET_ALARM_ACK_DATA * alarmack_data,
|
||||
BACNET_ERROR_CODE * error_code);
|
||||
|
||||
int Analog_Input_Alarm_Summary(
|
||||
unsigned index,
|
||||
BACNET_GET_ALARM_SUMMARY_DATA * getalarm_data);
|
||||
#endif
|
||||
|
||||
uint32_t Analog_Input_Create(
|
||||
uint32_t object_instance);
|
||||
bool Analog_Input_Delete(
|
||||
uint32_t object_instance);
|
||||
void Analog_Input_Cleanup(
|
||||
void);
|
||||
void Analog_Input_Init(
|
||||
void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,365 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet application glue for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bacnet_app.h"
|
||||
#include "mstimer_init.h"
|
||||
|
||||
#if defined(BACDL_MSTP)
|
||||
#include "dlenv.h"
|
||||
#include "bacnet/datalink/datalink.h"
|
||||
#elif defined(BACDL_BIP)
|
||||
#include "bip.h"
|
||||
#endif
|
||||
|
||||
#include "bacnet/basic/binding/address.h"
|
||||
#include "bacnet/basic/object/ai.h"
|
||||
#include "bacnet/basic/object/bi.h"
|
||||
#include "bacnet/basic/object/bo.h"
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
#include "bacnet/npdu.h"
|
||||
|
||||
#ifndef BACNET_IP_PORT
|
||||
#define BACNET_IP_PORT 47808
|
||||
#endif
|
||||
|
||||
#if defined(BACDL_BIP)
|
||||
static uint8_t PDUBuffer[BIP_MPDU_MAX];
|
||||
#else
|
||||
static uint8_t PDUBuffer[MAX_MPDU + 16];
|
||||
#endif
|
||||
|
||||
#define PLC_INPUT_COUNT 8
|
||||
#define PLC_RELAY_COUNT 4
|
||||
#define PLC_ANALOG_COUNT 2
|
||||
|
||||
#define PLC_AI_INSTANCE_TEMPERATURE 100
|
||||
#define PLC_AI_INSTANCE_FREE_HEAP_KB 101
|
||||
|
||||
static uint32_t Plc_Input_Instances[PLC_INPUT_COUNT];
|
||||
static uint32_t Plc_Relay_Instances[PLC_RELAY_COUNT];
|
||||
static uint32_t Plc_Analog_Instances[PLC_ANALOG_COUNT];
|
||||
|
||||
static const char *const Plc_Input_Names[PLC_INPUT_COUNT] = {
|
||||
"PLC Input 1", "PLC Input 2", "PLC Input 3", "PLC Input 4",
|
||||
"PLC Input 5", "PLC Input 6", "PLC Input 7", "PLC Input 8",
|
||||
};
|
||||
|
||||
static const char *const Plc_Input_Descriptions[PLC_INPUT_COUNT] = {
|
||||
"PLC Input 1", "PLC Input 2", "PLC Input 3", "PLC Input 4",
|
||||
"PLC Input 5", "PLC Input 6", "PLC Input 7", "PLC Input 8",
|
||||
};
|
||||
|
||||
static const char *const Plc_Relay_Names[PLC_RELAY_COUNT] = {
|
||||
"PLC Relay 1",
|
||||
"PLC Relay 2",
|
||||
"PLC Relay 3",
|
||||
"PLC Relay 4",
|
||||
};
|
||||
|
||||
static const char *const Plc_Relay_Descriptions[PLC_RELAY_COUNT] = {
|
||||
"PLC Relay 1",
|
||||
"PLC Relay 2",
|
||||
"PLC Relay 3",
|
||||
"PLC Relay 4",
|
||||
};
|
||||
|
||||
static const char *const Plc_Analog_Names[PLC_ANALOG_COUNT] = {
|
||||
"PLC Temperature",
|
||||
"Free Heap KB",
|
||||
};
|
||||
|
||||
static const char *const Plc_Analog_Descriptions[PLC_ANALOG_COUNT] = {
|
||||
"Internal PLC temperature in C",
|
||||
"Free heap memory in kilobytes",
|
||||
};
|
||||
|
||||
static object_functions_t My_Object_Table[] = {
|
||||
{ OBJECT_DEVICE,
|
||||
NULL,
|
||||
Device_Count,
|
||||
Device_Index_To_Instance,
|
||||
Device_Valid_Object_Instance_Number,
|
||||
Device_Object_Name,
|
||||
Device_Read_Property_Local,
|
||||
Device_Write_Property_Local,
|
||||
Device_Property_Lists,
|
||||
DeviceGetRRInfo,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
Device_Writable_Property_List },
|
||||
|
||||
{ OBJECT_BINARY_INPUT,
|
||||
Binary_Input_Init,
|
||||
Binary_Input_Count,
|
||||
Binary_Input_Index_To_Instance,
|
||||
Binary_Input_Valid_Instance,
|
||||
Binary_Input_Object_Name,
|
||||
Binary_Input_Read_Property,
|
||||
NULL,
|
||||
Binary_Input_Property_Lists,
|
||||
NULL,
|
||||
NULL,
|
||||
Binary_Input_Encode_Value_List,
|
||||
Binary_Input_Change_Of_Value,
|
||||
Binary_Input_Change_Of_Value_Clear,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
Binary_Input_Create,
|
||||
Binary_Input_Delete,
|
||||
NULL,
|
||||
Binary_Input_Writable_Property_List },
|
||||
|
||||
{ OBJECT_ANALOG_INPUT,
|
||||
Analog_Input_Init,
|
||||
Analog_Input_Count,
|
||||
Analog_Input_Index_To_Instance,
|
||||
Analog_Input_Valid_Instance,
|
||||
Analog_Input_Object_Name,
|
||||
Analog_Input_Read_Property,
|
||||
Analog_Input_Write_Property,
|
||||
Analog_Input_Property_Lists,
|
||||
NULL,
|
||||
NULL,
|
||||
Analog_Input_Encode_Value_List,
|
||||
Analog_Input_Change_Of_Value,
|
||||
Analog_Input_Change_Of_Value_Clear,
|
||||
Analog_Input_Intrinsic_Reporting,
|
||||
NULL,
|
||||
NULL,
|
||||
Analog_Input_Create,
|
||||
Analog_Input_Delete,
|
||||
NULL,
|
||||
Analog_Input_Writable_Property_List },
|
||||
|
||||
{ OBJECT_BINARY_OUTPUT,
|
||||
Binary_Output_Init,
|
||||
Binary_Output_Count,
|
||||
Binary_Output_Index_To_Instance,
|
||||
Binary_Output_Valid_Instance,
|
||||
Binary_Output_Object_Name,
|
||||
Binary_Output_Read_Property,
|
||||
Binary_Output_Write_Property,
|
||||
Binary_Output_Property_Lists,
|
||||
NULL,
|
||||
NULL,
|
||||
Binary_Output_Encode_Value_List,
|
||||
Binary_Output_Change_Of_Value,
|
||||
Binary_Output_Change_Of_Value_Clear,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
Binary_Output_Create,
|
||||
Binary_Output_Delete,
|
||||
NULL,
|
||||
Binary_Output_Writable_Property_List },
|
||||
|
||||
{ MAX_BACNET_OBJECT_TYPE,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create and initialize the BACnet objects exposed by this port
|
||||
*/
|
||||
static void init_server_objects(void)
|
||||
{
|
||||
uint32_t instance;
|
||||
|
||||
Device_Init(My_Object_Table);
|
||||
|
||||
for (uint8_t i = 0; i < PLC_INPUT_COUNT; i++) {
|
||||
instance = Binary_Input_Create(i);
|
||||
Plc_Input_Instances[i] = instance;
|
||||
(void)Binary_Input_Name_Set(instance, Plc_Input_Names[i]);
|
||||
(void)Binary_Input_Description_Set(instance, Plc_Input_Descriptions[i]);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < PLC_RELAY_COUNT; i++) {
|
||||
instance = Binary_Output_Create(i);
|
||||
Plc_Relay_Instances[i] = instance;
|
||||
(void)Binary_Output_Name_Set(instance, Plc_Relay_Names[i]);
|
||||
(void)Binary_Output_Description_Set(
|
||||
instance, Plc_Relay_Descriptions[i]);
|
||||
(void)Binary_Output_Present_Value_Set(
|
||||
instance, BINARY_INACTIVE, BACNET_MAX_PRIORITY);
|
||||
}
|
||||
|
||||
Plc_Analog_Instances[0] = Analog_Input_Create(PLC_AI_INSTANCE_TEMPERATURE);
|
||||
Plc_Analog_Instances[1] = Analog_Input_Create(PLC_AI_INSTANCE_FREE_HEAP_KB);
|
||||
|
||||
(void)Analog_Input_Name_Set(Plc_Analog_Instances[0], Plc_Analog_Names[0]);
|
||||
(void)Analog_Input_Description_Set(
|
||||
Plc_Analog_Instances[0], Plc_Analog_Descriptions[0]);
|
||||
(void)Analog_Input_Units_Set(
|
||||
Plc_Analog_Instances[0], UNITS_DEGREES_CELSIUS);
|
||||
|
||||
(void)Analog_Input_Name_Set(Plc_Analog_Instances[1], Plc_Analog_Names[1]);
|
||||
(void)Analog_Input_Description_Set(
|
||||
Plc_Analog_Instances[1], Plc_Analog_Descriptions[1]);
|
||||
(void)Analog_Input_Units_Set(Plc_Analog_Instances[1], UNITS_NO_UNITS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the BACnet application and selected datalink
|
||||
*/
|
||||
void bacnet_app_init(void)
|
||||
{
|
||||
systimer_init();
|
||||
|
||||
address_init();
|
||||
Device_Set_Object_Instance_Number(12345);
|
||||
init_server_objects();
|
||||
|
||||
#if defined(BACDL_MSTP)
|
||||
if (!m5_dlenv_init(2)) {
|
||||
for (;;) { }
|
||||
}
|
||||
#elif defined(BACDL_BIP)
|
||||
if (!bip_init((uint16_t)BACNET_IP_PORT)) {
|
||||
for (;;) { }
|
||||
}
|
||||
#endif
|
||||
|
||||
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
|
||||
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
|
||||
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has);
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple);
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property);
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL,
|
||||
handler_device_communication_control);
|
||||
|
||||
Send_I_Am(&Handler_Transmit_Buffer[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process one BACnet polling cycle and advance timers
|
||||
*/
|
||||
void bacnet_app_tick(void)
|
||||
{
|
||||
BACNET_ADDRESS src = { 0 };
|
||||
uint16_t pdu_len = 0;
|
||||
|
||||
#if defined(BACDL_BIP)
|
||||
pdu_len = bip_receive(&src, &PDUBuffer[0], sizeof(PDUBuffer), 0);
|
||||
#else
|
||||
pdu_len = datalink_receive(&src, &PDUBuffer[0], MAX_MPDU, 0);
|
||||
#endif
|
||||
if (pdu_len) {
|
||||
npdu_handler(&src, &PDUBuffer[0], pdu_len);
|
||||
}
|
||||
|
||||
tsm_timer_milliseconds(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of exposed PLC binary inputs
|
||||
* @return number of binary inputs
|
||||
*/
|
||||
uint8_t bacnet_app_input_count(void)
|
||||
{
|
||||
return PLC_INPUT_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of exposed PLC relay outputs
|
||||
* @return number of binary outputs
|
||||
*/
|
||||
uint8_t bacnet_app_relay_count(void)
|
||||
{
|
||||
return PLC_RELAY_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update a BACnet binary input from PLC state
|
||||
* @param index zero-based PLC input index
|
||||
* @param active input state to publish
|
||||
*/
|
||||
void bacnet_app_input_set(uint8_t index, bool active)
|
||||
{
|
||||
uint32_t instance;
|
||||
|
||||
if (index >= PLC_INPUT_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
instance = Plc_Input_Instances[index];
|
||||
|
||||
(void)Binary_Input_Present_Value_Set(
|
||||
instance, active ? BINARY_ACTIVE : BINARY_INACTIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the commanded state of a BACnet relay output
|
||||
* @param index zero-based relay index
|
||||
* @return true if the relay should be active
|
||||
*/
|
||||
bool bacnet_app_relay_get(uint8_t index)
|
||||
{
|
||||
uint32_t instance;
|
||||
|
||||
if (index >= PLC_RELAY_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance = Plc_Relay_Instances[index];
|
||||
|
||||
return (Binary_Output_Present_Value(instance) == BINARY_ACTIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the analog input that reports PLC temperature
|
||||
* @param value_celsius temperature in degrees Celsius
|
||||
*/
|
||||
void bacnet_app_temperature_set(float value_celsius)
|
||||
{
|
||||
Analog_Input_Present_Value_Set(Plc_Analog_Instances[0], value_celsius);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the analog input that reports free heap memory
|
||||
* @param value_kb free heap in kilobytes
|
||||
*/
|
||||
void bacnet_app_free_heap_kb_set(float value_kb)
|
||||
{
|
||||
Analog_Input_Present_Value_Set(Plc_Analog_Instances[1], value_kb);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief Public BACnet application hooks for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#ifndef M5STAMPLC_BACNET_APP_H
|
||||
#define M5STAMPLC_BACNET_APP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the BACnet application and datalink bindings
|
||||
*/
|
||||
void bacnet_app_init(void);
|
||||
|
||||
/**
|
||||
* @brief Run one BACnet processing cycle
|
||||
*/
|
||||
void bacnet_app_tick(void);
|
||||
|
||||
/**
|
||||
* @brief Get the number of published PLC inputs
|
||||
* @return number of PLC inputs
|
||||
*/
|
||||
uint8_t bacnet_app_input_count(void);
|
||||
|
||||
/**
|
||||
* @brief Get the number of published PLC relays
|
||||
* @return number of PLC relays
|
||||
*/
|
||||
uint8_t bacnet_app_relay_count(void);
|
||||
|
||||
/**
|
||||
* @brief Push a PLC input state into the BACnet object model
|
||||
* @param index zero-based input index
|
||||
* @param active input state
|
||||
*/
|
||||
void bacnet_app_input_set(uint8_t index, bool active);
|
||||
|
||||
/**
|
||||
* @brief Read the requested state for a PLC relay
|
||||
* @param index zero-based relay index
|
||||
* @return true if the relay should be active
|
||||
*/
|
||||
bool bacnet_app_relay_get(uint8_t index);
|
||||
|
||||
/**
|
||||
* @brief Update the published PLC temperature value
|
||||
* @param value_celsius temperature in degrees Celsius
|
||||
*/
|
||||
void bacnet_app_temperature_set(float value_celsius);
|
||||
|
||||
/**
|
||||
* @brief Update the published free heap value
|
||||
* @param value_kb free heap in kilobytes
|
||||
*/
|
||||
void bacnet_app_free_heap_kb_set(float value_kb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet/IP datalink implementation for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacint.h"
|
||||
#include "bacnet/datalink/bvlc.h"
|
||||
|
||||
#include "bip.h"
|
||||
#include "bvlc.h"
|
||||
|
||||
#if PRINT_ENABLED || DEBUG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#define MAX_SOCK_NUM 8
|
||||
|
||||
static uint8_t BIP_Socket = MAX_SOCK_NUM;
|
||||
static uint16_t BIP_Port = 0;
|
||||
static uint8_t BIP_Address[4] = { 0, 0, 0, 0 };
|
||||
static uint8_t BIP_Broadcast_Address[4] = { 0, 0, 0, 0 };
|
||||
|
||||
/**
|
||||
* @brief Convert a 4-byte IP address into a uint32_t value
|
||||
* @param bip_address pointer to the 4-byte address
|
||||
* @return 32-bit packed address
|
||||
*/
|
||||
uint32_t convertBIP_Address2uint32(const uint8_t *bip_address)
|
||||
{
|
||||
return ((uint32_t)bip_address[0] << 24) | ((uint32_t)bip_address[1] << 16) |
|
||||
((uint32_t)bip_address[2] << 8) | ((uint32_t)bip_address[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a uint32_t IP address into a 4-byte array
|
||||
* @param ip packed IP address
|
||||
* @param address destination buffer for the 4-byte address
|
||||
*/
|
||||
void convertUint32Address_2_uint8Address(uint32_t ip, uint8_t *address)
|
||||
{
|
||||
address[0] = (uint8_t)(ip >> 24);
|
||||
address[1] = (uint8_t)(ip >> 16);
|
||||
address[2] = (uint8_t)(ip >> 8);
|
||||
address[3] = (uint8_t)(ip >> 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store the active socket identifier used by the BACnet/IP layer
|
||||
* @param sock_fd socket identifier
|
||||
*/
|
||||
void bip_set_socket(uint8_t sock_fd)
|
||||
{
|
||||
BIP_Socket = sock_fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the stored BACnet/IP socket identifier
|
||||
* @return socket identifier
|
||||
*/
|
||||
uint8_t bip_socket(void)
|
||||
{
|
||||
return BIP_Socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the BACnet/IP socket state is valid
|
||||
* @return true if the stored socket identifier is valid
|
||||
*/
|
||||
bool bip_valid(void)
|
||||
{
|
||||
return (BIP_Socket < MAX_SOCK_NUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store the local IPv4 address used by BACnet/IP
|
||||
* @param net_address pointer to the 4-byte local address
|
||||
*/
|
||||
void bip_set_addr(const uint8_t *net_address)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
BIP_Address[i] = net_address[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the stored local IPv4 address
|
||||
* @return pointer to the 4-byte local address
|
||||
*/
|
||||
uint8_t *bip_get_addr(void)
|
||||
{
|
||||
return BIP_Address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store the broadcast IPv4 address used by BACnet/IP
|
||||
* @param net_address pointer to the 4-byte broadcast address
|
||||
*/
|
||||
void bip_set_broadcast_addr(const uint8_t *net_address)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
BIP_Broadcast_Address[i] = net_address[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the stored broadcast IPv4 address
|
||||
* @return pointer to the 4-byte broadcast address
|
||||
*/
|
||||
uint8_t *bip_get_broadcast_addr(void)
|
||||
{
|
||||
return BIP_Broadcast_Address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store the UDP port used by BACnet/IP
|
||||
* @param port UDP port number
|
||||
*/
|
||||
void bip_set_port(uint16_t port)
|
||||
{
|
||||
BIP_Port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the UDP port used by BACnet/IP
|
||||
* @return UDP port number
|
||||
*/
|
||||
uint16_t bip_get_port(void)
|
||||
{
|
||||
return BIP_Port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode a BACnet/IP MAC address into IPv4 address and port parts
|
||||
* @param bac_addr source BACnet address
|
||||
* @param address destination IPv4 address buffer
|
||||
* @param port destination UDP port pointer
|
||||
* @return number of bytes decoded from the MAC address
|
||||
*/
|
||||
static int bip_decode_bip_address(
|
||||
const BACNET_ADDRESS *bac_addr, uint8_t *address, uint16_t *port)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (bac_addr) {
|
||||
memcpy(address, &bac_addr->mac[0], 4);
|
||||
memcpy(port, &bac_addr->mac[4], 2);
|
||||
len = 6;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a BACnet NPDU over BACnet/IP
|
||||
* @param dest destination BACnet address
|
||||
* @param npdu_data NPDU metadata
|
||||
* @param pdu payload buffer
|
||||
* @param pdu_len payload length in bytes
|
||||
* @return number of bytes sent, or -1 on error
|
||||
*/
|
||||
int bip_send_pdu(
|
||||
BACNET_ADDRESS *dest,
|
||||
BACNET_NPDU_DATA *npdu_data,
|
||||
uint8_t *pdu,
|
||||
unsigned pdu_len)
|
||||
{
|
||||
uint8_t mtu[BIP_MPDU_MAX] = { 0 };
|
||||
int mtu_len = 0;
|
||||
int bytes_sent = 0;
|
||||
uint8_t address[] = { 0, 0, 0, 0 };
|
||||
uint16_t port = 0;
|
||||
uint8_t i;
|
||||
|
||||
(void)npdu_data;
|
||||
|
||||
if (BIP_Socket >= MAX_SOCK_NUM) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
mtu[0] = BVLL_TYPE_BACNET_IP;
|
||||
if ((dest->net == BACNET_BROADCAST_NETWORK) ||
|
||||
((dest->net > 0) && (dest->len == 0)) || (dest->mac_len == 0)) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
address[i] = BIP_Broadcast_Address[i];
|
||||
}
|
||||
port = BIP_Port;
|
||||
mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
||||
} else if (dest->mac_len == 6) {
|
||||
bip_decode_bip_address(dest, address, &port);
|
||||
mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
mtu_len = 2;
|
||||
mtu_len += encode_unsigned16(&mtu[mtu_len], (uint16_t)(pdu_len + 4));
|
||||
memcpy(&mtu[mtu_len], pdu, pdu_len);
|
||||
mtu_len += (int)pdu_len;
|
||||
|
||||
bytes_sent = bip_socket_send(address, port, mtu, (uint16_t)mtu_len);
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Receive a BACnet/IP NPDU and strip BVLC framing
|
||||
* @param src source BACnet address
|
||||
* @param pdu receive buffer for NPDU data
|
||||
* @param max_pdu receive buffer size
|
||||
* @param timeout receive timeout in milliseconds
|
||||
* @return NPDU length in bytes, or 0 if no valid frame was received
|
||||
*/
|
||||
uint16_t bip_receive(
|
||||
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout)
|
||||
{
|
||||
int received_bytes = 0;
|
||||
int max = 0;
|
||||
uint16_t pdu_len = 0;
|
||||
uint8_t src_addr[] = { 0, 0, 0, 0 };
|
||||
uint16_t src_port = 0;
|
||||
uint16_t i = 0;
|
||||
int function = 0;
|
||||
|
||||
(void)timeout;
|
||||
|
||||
if (BIP_Socket >= MAX_SOCK_NUM) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
received_bytes = bip_socket_receive(pdu, max_pdu, src_addr, &src_port);
|
||||
if (received_bytes <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pdu[0] != BVLL_TYPE_BACNET_IP) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
max = (int)max_pdu - received_bytes;
|
||||
if (max > 0) {
|
||||
if (max > 16) {
|
||||
max = 16;
|
||||
}
|
||||
memset(&pdu[received_bytes], 0, (size_t)max);
|
||||
}
|
||||
|
||||
if (bvlc_for_non_bbmd(src_addr, &src_port, pdu, (uint16_t)received_bytes) >
|
||||
0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function = pico_bvlc_get_function_code();
|
||||
if ((function == BVLC_ORIGINAL_UNICAST_NPDU) ||
|
||||
(function == BVLC_ORIGINAL_BROADCAST_NPDU)) {
|
||||
if ((convertBIP_Address2uint32(src_addr) ==
|
||||
convertBIP_Address2uint32(BIP_Address)) &&
|
||||
(src_port == BIP_Port)) {
|
||||
pdu_len = 0;
|
||||
} else {
|
||||
src->mac_len = 6;
|
||||
memcpy(&src->mac[0], &src_addr, 4);
|
||||
memcpy(&src->mac[4], &src_port, 2);
|
||||
(void)decode_unsigned16(&pdu[2], &pdu_len);
|
||||
pdu_len -= 4;
|
||||
if (pdu_len < max_pdu) {
|
||||
for (i = 0; i < pdu_len; i++) {
|
||||
pdu[i] = pdu[4 + i];
|
||||
}
|
||||
} else {
|
||||
pdu_len = 0;
|
||||
}
|
||||
}
|
||||
} else if (function == BVLC_FORWARDED_NPDU) {
|
||||
memcpy(&src_addr, &pdu[4], 4);
|
||||
memcpy(&src_port, &pdu[8], 2);
|
||||
if ((convertBIP_Address2uint32(src_addr) ==
|
||||
convertBIP_Address2uint32(BIP_Address)) &&
|
||||
(src_port == BIP_Port)) {
|
||||
pdu_len = 0;
|
||||
} else {
|
||||
src->mac_len = 6;
|
||||
memcpy(&src->mac[0], &src_addr, 4);
|
||||
memcpy(&src->mac[4], &src_port, 2);
|
||||
(void)decode_unsigned16(&pdu[2], &pdu_len);
|
||||
pdu_len -= 10;
|
||||
if (pdu_len < max_pdu) {
|
||||
for (i = 0; i < pdu_len; i++) {
|
||||
pdu[i] = pdu[10 + i];
|
||||
}
|
||||
} else {
|
||||
pdu_len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the local BACnet/IP address structure
|
||||
* @param my_address destination BACnet address
|
||||
*/
|
||||
void bip_get_my_address(BACNET_ADDRESS *my_address)
|
||||
{
|
||||
if (my_address) {
|
||||
my_address->mac_len = 6;
|
||||
memcpy(&my_address->mac[0], &BIP_Address[0], 4);
|
||||
memcpy(&my_address->mac[4], &BIP_Port, 2);
|
||||
my_address->net = 0;
|
||||
my_address->len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the BACnet/IP broadcast address structure
|
||||
* @param dest destination BACnet address
|
||||
*/
|
||||
void bip_get_broadcast_address(BACNET_ADDRESS *dest)
|
||||
{
|
||||
if (dest) {
|
||||
dest->mac_len = 6;
|
||||
memcpy(&dest->mac[0], &BIP_Broadcast_Address[0], 4);
|
||||
memcpy(&dest->mac[4], &BIP_Port, 2);
|
||||
dest->net = BACNET_BROADCAST_NETWORK;
|
||||
dest->len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a raw BACnet/IP MPDU to a specific host and port
|
||||
* @param dest destination IPv4 address and port
|
||||
* @param mtu raw BVLC/NPDU buffer
|
||||
* @param mtu_len buffer length in bytes
|
||||
* @return number of bytes sent, or -1 on error
|
||||
*/
|
||||
int bip_send_mpdu(
|
||||
const BACNET_IP_ADDRESS *dest, const uint8_t *mtu, uint16_t mtu_len)
|
||||
{
|
||||
return bip_socket_send(dest->address, dest->port, mtu, mtu_len);
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet/IP datalink interface for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#ifndef M5STAMPLC_BIP_H
|
||||
#define M5STAMPLC_BIP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/npdu.h"
|
||||
#include "bacnet/datalink/bvlc.h"
|
||||
|
||||
#define BIP_HEADER_MAX (1 + 1 + 2)
|
||||
#ifndef BIP_MPDU_MAX
|
||||
#define BIP_MPDU_MAX (BIP_HEADER_MAX + MAX_PDU)
|
||||
#endif
|
||||
|
||||
#define BVLL_TYPE_BACNET_IP (0x81)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the UDP socket backend used by BACnet/IP
|
||||
* @param port UDP port to bind
|
||||
* @return true if the socket backend was initialized
|
||||
*/
|
||||
bool bip_socket_init(uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Send a raw UDP payload for BACnet/IP
|
||||
* @param dest_addr destination IPv4 address
|
||||
* @param dest_port destination UDP port
|
||||
* @param mtu payload buffer
|
||||
* @param mtu_len payload length
|
||||
* @return number of bytes sent, or -1 on error
|
||||
*/
|
||||
int bip_socket_send(
|
||||
const uint8_t *dest_addr,
|
||||
uint16_t dest_port,
|
||||
const uint8_t *mtu,
|
||||
uint16_t mtu_len);
|
||||
/**
|
||||
* @brief Receive a raw UDP payload for BACnet/IP
|
||||
* @param buf receive buffer
|
||||
* @param buf_len receive buffer size
|
||||
* @param src_addr source IPv4 address
|
||||
* @param src_port source UDP port
|
||||
* @return number of bytes received
|
||||
*/
|
||||
int bip_socket_receive(
|
||||
uint8_t *buf, uint16_t buf_len, uint8_t *src_addr, uint16_t *src_port);
|
||||
|
||||
/**
|
||||
* @brief Close the UDP socket backend used by BACnet/IP
|
||||
*/
|
||||
void bip_socket_cleanup(void);
|
||||
|
||||
/**
|
||||
* @brief Read the current local IP address and subnet mask
|
||||
* @param local_addr destination for the local IPv4 address
|
||||
* @param netmask destination for the subnet mask
|
||||
* @return true if the network information was available
|
||||
*/
|
||||
bool bip_get_local_network_info(uint8_t *local_addr, uint8_t *netmask);
|
||||
|
||||
/**
|
||||
* @brief Initialize the BACnet/IP datalink
|
||||
* @param port UDP port to use
|
||||
* @return true if initialization succeeded
|
||||
*/
|
||||
bool bip_init(uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Refresh the local and broadcast interface addresses
|
||||
*/
|
||||
void bip_set_interface(void);
|
||||
|
||||
/**
|
||||
* @brief Shut down the BACnet/IP datalink
|
||||
*/
|
||||
void bip_cleanup(void);
|
||||
|
||||
/**
|
||||
* @brief Convert a 4-byte address into a 32-bit value
|
||||
* @param bip_address pointer to IPv4 address bytes
|
||||
* @return packed address value
|
||||
*/
|
||||
uint32_t convertBIP_Address2uint32(const uint8_t *bip_address);
|
||||
|
||||
/**
|
||||
* @brief Convert a 32-bit address into a 4-byte array
|
||||
* @param ip packed address value
|
||||
* @param address destination buffer
|
||||
*/
|
||||
void convertUint32Address_2_uint8Address(uint32_t ip, uint8_t *address);
|
||||
|
||||
/**
|
||||
* @brief Store the current socket identifier
|
||||
* @param sock_fd socket identifier
|
||||
*/
|
||||
void bip_set_socket(uint8_t sock_fd);
|
||||
|
||||
/**
|
||||
* @brief Get the current socket identifier
|
||||
* @return socket identifier
|
||||
*/
|
||||
uint8_t bip_socket(void);
|
||||
|
||||
/**
|
||||
* @brief Check whether the BACnet/IP datalink is valid
|
||||
* @return true if the datalink is ready to use
|
||||
*/
|
||||
bool bip_valid(void);
|
||||
|
||||
/**
|
||||
* @brief Build the broadcast BACnet/IP address
|
||||
* @param dest destination address structure
|
||||
*/
|
||||
void bip_get_broadcast_address(BACNET_ADDRESS *dest);
|
||||
|
||||
/**
|
||||
* @brief Build the local BACnet/IP address
|
||||
* @param my_address destination address structure
|
||||
*/
|
||||
void bip_get_my_address(BACNET_ADDRESS *my_address);
|
||||
|
||||
/**
|
||||
* @brief Send a BACnet NPDU over BACnet/IP
|
||||
* @param dest destination BACnet address
|
||||
* @param npdu_data NPDU metadata
|
||||
* @param pdu payload buffer
|
||||
* @param pdu_len payload length in bytes
|
||||
* @return number of bytes sent, or -1 on error
|
||||
*/
|
||||
int bip_send_pdu(
|
||||
BACNET_ADDRESS *dest,
|
||||
BACNET_NPDU_DATA *npdu_data,
|
||||
uint8_t *pdu,
|
||||
unsigned pdu_len);
|
||||
|
||||
/**
|
||||
* @brief Send a raw BACnet/IP MPDU
|
||||
* @param dest destination IP address and port
|
||||
* @param mtu raw buffer to send
|
||||
* @param mtu_len buffer length in bytes
|
||||
* @return number of bytes sent, or -1 on error
|
||||
*/
|
||||
int bip_send_mpdu(
|
||||
const BACNET_IP_ADDRESS *dest, const uint8_t *mtu, uint16_t mtu_len);
|
||||
|
||||
/**
|
||||
* @brief Receive a BACnet/IP NPDU
|
||||
* @param src source BACnet address
|
||||
* @param pdu receive buffer
|
||||
* @param max_pdu receive buffer size
|
||||
* @param timeout receive timeout in milliseconds
|
||||
* @return number of NPDU bytes received
|
||||
*/
|
||||
uint16_t bip_receive(
|
||||
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout);
|
||||
|
||||
/**
|
||||
* @brief Store the UDP port used by BACnet/IP
|
||||
* @param port UDP port number
|
||||
*/
|
||||
void bip_set_port(uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Get the UDP port used by BACnet/IP
|
||||
* @return UDP port number
|
||||
*/
|
||||
uint16_t bip_get_port(void);
|
||||
|
||||
/**
|
||||
* @brief Store the local IPv4 address
|
||||
* @param net_address pointer to the 4-byte address
|
||||
*/
|
||||
void bip_set_addr(const uint8_t *net_address);
|
||||
|
||||
/**
|
||||
* @brief Get the local IPv4 address
|
||||
* @return pointer to the stored address
|
||||
*/
|
||||
uint8_t *bip_get_addr(void);
|
||||
|
||||
/**
|
||||
* @brief Store the IPv4 broadcast address
|
||||
* @param net_address pointer to the 4-byte broadcast address
|
||||
*/
|
||||
void bip_set_broadcast_addr(const uint8_t *net_address);
|
||||
|
||||
/**
|
||||
* @brief Get the stored IPv4 broadcast address
|
||||
* @return pointer to the stored broadcast address
|
||||
*/
|
||||
uint8_t *bip_get_broadcast_addr(void);
|
||||
|
||||
/**
|
||||
* @brief Resolve a host name into an address when supported
|
||||
* @param host_name host name to resolve
|
||||
* @return resolved address value, or 0 when unsupported
|
||||
*/
|
||||
long bip_getaddrbyname(const char *host_name);
|
||||
|
||||
/**
|
||||
* @brief Enable BACnet/IP debug behavior
|
||||
*/
|
||||
void bip_debug_enable(void);
|
||||
|
||||
/**
|
||||
* @brief Disable BACnet/IP debug behavior
|
||||
*/
|
||||
void bip_debug_disable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+78
-38
@@ -1,56 +1,96 @@
|
||||
//
|
||||
// Copyleft F.Chaxel 2017
|
||||
//
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet/IP initialization helpers for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include "bip.h"
|
||||
|
||||
#include "bacnet/datalink/bip.h"
|
||||
static bool BIP_Debug = false;
|
||||
|
||||
long bip_get_addr_by_name(const char *host_name)
|
||||
/**
|
||||
* @brief Enable BACnet/IP debug mode
|
||||
*/
|
||||
void bip_debug_enable(void)
|
||||
{
|
||||
BIP_Debug = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable BACnet/IP debug mode
|
||||
*/
|
||||
void bip_debug_disable(void)
|
||||
{
|
||||
BIP_Debug = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resolve a host name into an address when supported
|
||||
* @param host_name host name to resolve
|
||||
* @return resolved address, or 0 when unsupported by this port
|
||||
*/
|
||||
long bip_getaddrbyname(const char *host_name)
|
||||
{
|
||||
(void)host_name;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bip_set_interface(const char *ifname)
|
||||
/**
|
||||
* @brief Refresh the local and broadcast BACnet/IP addresses
|
||||
*/
|
||||
void bip_set_interface(void)
|
||||
{
|
||||
uint8_t local_address[] = { 0, 0, 0, 0 };
|
||||
uint8_t broadcast_address[] = { 0, 0, 0, 0 };
|
||||
uint8_t netmask[] = { 0, 0, 0, 0 };
|
||||
uint8_t inverted_netmask[] = { 0, 0, 0, 0 };
|
||||
int i;
|
||||
|
||||
if (!bip_get_local_network_info(local_address, netmask)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bip_set_addr(local_address);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
inverted_netmask[i] = (uint8_t)~netmask[i];
|
||||
broadcast_address[i] =
|
||||
(uint8_t)(local_address[i] | inverted_netmask[i]);
|
||||
}
|
||||
|
||||
bip_set_broadcast_addr(broadcast_address);
|
||||
}
|
||||
|
||||
void bip_cleanup(void)
|
||||
/**
|
||||
* @brief Initialize the BACnet/IP datalink for the selected UDP port
|
||||
* @param port UDP port number
|
||||
* @return true if initialization succeeded
|
||||
*/
|
||||
bool bip_init(uint16_t port)
|
||||
{
|
||||
close(bip_socket());
|
||||
bip_set_socket(-1);
|
||||
}
|
||||
(void)BIP_Debug;
|
||||
|
||||
bool bip_init(char *ifname)
|
||||
{
|
||||
tcpip_adapter_ip_info_t ip_info = { 0 };
|
||||
if (!bip_socket_init(port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int value = 1;
|
||||
bip_set_interface();
|
||||
bip_set_port(port);
|
||||
|
||||
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);
|
||||
|
||||
bip_set_interface(ifname);
|
||||
bip_set_port(0xBAC0U);
|
||||
bip_set_addr(ip_info.ip.addr);
|
||||
bip_set_broadcast_addr(
|
||||
(ip_info.ip.addr & ip_info.netmask.addr) | (~ip_info.netmask.addr));
|
||||
|
||||
int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
struct sockaddr_in saddr = { 0 };
|
||||
|
||||
saddr.sin_family = PF_INET;
|
||||
saddr.sin_port = htons(0xBAC0U);
|
||||
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
|
||||
|
||||
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&value, sizeof(value));
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&value, sizeof(value));
|
||||
|
||||
bip_set_socket(sock);
|
||||
bip_set_socket(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shut down the BACnet/IP datalink
|
||||
*/
|
||||
void bip_cleanup(void)
|
||||
{
|
||||
if (bip_valid()) {
|
||||
bip_socket_cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief UDP socket bridge used by BACnet/IP on the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE)
|
||||
#include <ETH.h>
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include "bip.h"
|
||||
}
|
||||
|
||||
static WiFiUDP BipUdp;
|
||||
static bool BipSocketOpen = false;
|
||||
|
||||
/**
|
||||
* @brief Check whether the selected network interface is up
|
||||
* @return true if the interface is connected
|
||||
*/
|
||||
static bool network_connected(void)
|
||||
{
|
||||
#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE)
|
||||
return ETH.linkUp();
|
||||
#else
|
||||
return (WiFi.status() == WL_CONNECTED);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the local IP address for the selected network interface
|
||||
* @return local IP address
|
||||
*/
|
||||
static IPAddress network_local_ip(void)
|
||||
{
|
||||
#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE)
|
||||
return ETH.localIP();
|
||||
#else
|
||||
return WiFi.localIP();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the subnet mask for the selected network interface
|
||||
* @return subnet mask
|
||||
*/
|
||||
static IPAddress network_subnet_mask(void)
|
||||
{
|
||||
#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE)
|
||||
return ETH.subnetMask();
|
||||
#else
|
||||
return WiFi.subnetMask();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the UDP socket used by BACnet/IP
|
||||
* @param port UDP port to bind
|
||||
* @return true if the socket was opened successfully
|
||||
*/
|
||||
extern "C" bool bip_socket_init(uint16_t port)
|
||||
{
|
||||
if (!network_connected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BipUdp.stop();
|
||||
BipSocketOpen = (BipUdp.begin(port) == 1);
|
||||
return BipSocketOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a UDP packet for BACnet/IP
|
||||
* @param dest_addr destination IPv4 address
|
||||
* @param dest_port destination UDP port
|
||||
* @param mtu payload buffer
|
||||
* @param mtu_len payload length in bytes
|
||||
* @return number of bytes sent, or -1 on error
|
||||
*/
|
||||
extern "C" int bip_socket_send(
|
||||
const uint8_t *dest_addr,
|
||||
uint16_t dest_port,
|
||||
const uint8_t *mtu,
|
||||
uint16_t mtu_len)
|
||||
{
|
||||
if (!BipSocketOpen || !dest_addr || !mtu || (mtu_len == 0U)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
IPAddress ip(dest_addr[0], dest_addr[1], dest_addr[2], dest_addr[3]);
|
||||
if (!BipUdp.beginPacket(ip, dest_port)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t written = BipUdp.write(mtu, mtu_len);
|
||||
if (!BipUdp.endPacket()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (written == mtu_len) ? (int)written : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Receive a UDP packet for BACnet/IP
|
||||
* @param buf receive buffer
|
||||
* @param buf_len receive buffer size
|
||||
* @param src_addr source IPv4 address
|
||||
* @param src_port source UDP port
|
||||
* @return number of bytes received
|
||||
*/
|
||||
extern "C" int bip_socket_receive(
|
||||
uint8_t *buf, uint16_t buf_len, uint8_t *src_addr, uint16_t *src_port)
|
||||
{
|
||||
if (!BipSocketOpen || !buf || !src_addr || !src_port || (buf_len == 0U)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int packet_size = BipUdp.parsePacket();
|
||||
if (packet_size <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
IPAddress remote = BipUdp.remoteIP();
|
||||
src_addr[0] = remote[0];
|
||||
src_addr[1] = remote[1];
|
||||
src_addr[2] = remote[2];
|
||||
src_addr[3] = remote[3];
|
||||
*src_port = BipUdp.remotePort();
|
||||
|
||||
int to_read = packet_size;
|
||||
if (to_read > (int)buf_len) {
|
||||
to_read = (int)buf_len;
|
||||
}
|
||||
|
||||
int len = BipUdp.read(buf, to_read);
|
||||
return (len > 0) ? len : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close the UDP socket used by BACnet/IP
|
||||
*/
|
||||
extern "C" void bip_socket_cleanup(void)
|
||||
{
|
||||
BipUdp.stop();
|
||||
BipSocketOpen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the local network address and subnet mask
|
||||
* @param local_addr destination IPv4 address buffer
|
||||
* @param netmask destination subnet mask buffer
|
||||
* @return true if network information was available
|
||||
*/
|
||||
extern "C" bool
|
||||
bip_get_local_network_info(uint8_t *local_addr, uint8_t *netmask)
|
||||
{
|
||||
if (!local_addr || !netmask || !network_connected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IPAddress ip = network_local_ip();
|
||||
IPAddress mask = network_subnet_mask();
|
||||
local_addr[0] = ip[0];
|
||||
local_addr[1] = ip[1];
|
||||
local_addr[2] = ip[2];
|
||||
local_addr[3] = ip[3];
|
||||
|
||||
netmask[0] = mask[0];
|
||||
netmask[1] = mask[1];
|
||||
netmask[2] = mask[2];
|
||||
netmask[3] = mask[3];
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
/* Binary Output Objects - customize for your use */
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
#include "bacnet/bacapp.h"
|
||||
#include "bacnet/config.h" /* the custom stuff */
|
||||
#include "bacnet/rp.h"
|
||||
#include "bacnet/wp.h"
|
||||
#include "bacnet/basic/object/bo.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
|
||||
#ifndef MAX_BINARY_OUTPUTS
|
||||
#define MAX_BINARY_OUTPUTS 1
|
||||
#endif
|
||||
|
||||
/* When all the priorities are level null, the present value returns */
|
||||
/* the Relinquish Default value */
|
||||
#define RELINQUISH_DEFAULT BINARY_INACTIVE
|
||||
/* Here is our Priority Array.*/
|
||||
static BACNET_BINARY_PV Binary_Output_Level[MAX_BINARY_OUTPUTS]
|
||||
[BACNET_MAX_PRIORITY];
|
||||
/* Writable out-of-service allows others to play with our Present Value */
|
||||
/* without changing the physical output */
|
||||
static bool Out_Of_Service[MAX_BINARY_OUTPUTS];
|
||||
|
||||
/* These three arrays are used by the ReadPropertyMultiple handler */
|
||||
static const int32_t Binary_Output_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
|
||||
PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PRESENT_VALUE, PROP_STATUS_FLAGS,
|
||||
PROP_EVENT_STATE, PROP_OUT_OF_SERVICE, PROP_POLARITY, PROP_PRIORITY_ARRAY,
|
||||
PROP_RELINQUISH_DEFAULT, -1 };
|
||||
|
||||
static const int32_t Binary_Output_Properties_Optional[] = { PROP_DESCRIPTION,
|
||||
PROP_ACTIVE_TEXT, PROP_INACTIVE_TEXT, -1 };
|
||||
|
||||
static const int32_t Binary_Output_Properties_Proprietary[] = { -1 };
|
||||
|
||||
void Binary_Output_Property_Lists(
|
||||
const int32_t **pRequired, const int32_t **pOptional, const int32_t **pProprietary)
|
||||
{
|
||||
if (pRequired)
|
||||
*pRequired = Binary_Output_Properties_Required;
|
||||
if (pOptional)
|
||||
*pOptional = Binary_Output_Properties_Optional;
|
||||
if (pProprietary)
|
||||
*pProprietary = Binary_Output_Properties_Proprietary;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void Binary_Output_Init(void)
|
||||
{
|
||||
unsigned i, j;
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
|
||||
/* initialize all the analog output priority arrays to NULL */
|
||||
for (i = 0; i < MAX_BINARY_OUTPUTS; i++) {
|
||||
for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
|
||||
Binary_Output_Level[i][j] = BINARY_NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need validate that the */
|
||||
/* given instance exists */
|
||||
bool Binary_Output_Valid_Instance(uint32_t object_instance)
|
||||
{
|
||||
if (object_instance < MAX_BINARY_OUTPUTS)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then count how many you have */
|
||||
unsigned Binary_Output_Count(void)
|
||||
{
|
||||
return MAX_BINARY_OUTPUTS;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the instance */
|
||||
/* that correlates to the correct index */
|
||||
uint32_t Binary_Output_Index_To_Instance(unsigned index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the index */
|
||||
/* that correlates to the correct instance number */
|
||||
unsigned Binary_Output_Instance_To_Index(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = MAX_BINARY_OUTPUTS;
|
||||
|
||||
if (object_instance < MAX_BINARY_OUTPUTS)
|
||||
index = object_instance;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
BACNET_BINARY_PV Binary_Output_Present_Value(uint32_t object_instance)
|
||||
{
|
||||
BACNET_BINARY_PV value = RELINQUISH_DEFAULT;
|
||||
unsigned index = 0;
|
||||
unsigned i = 0;
|
||||
|
||||
index = Binary_Output_Instance_To_Index(object_instance);
|
||||
if (index < MAX_BINARY_OUTPUTS) {
|
||||
for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
|
||||
if (Binary_Output_Level[index][i] != BINARY_NULL) {
|
||||
value = Binary_Output_Level[index][i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool Binary_Output_Out_Of_Service(uint32_t object_instance)
|
||||
{
|
||||
bool value = false;
|
||||
unsigned index = 0;
|
||||
|
||||
index = Binary_Output_Instance_To_Index(object_instance);
|
||||
if (index < MAX_BINARY_OUTPUTS) {
|
||||
value = Out_Of_Service[index];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* note: the object name must be unique within this device */
|
||||
|
||||
bool Binary_Output_Object_Name(
|
||||
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
||||
{
|
||||
static char text[32] = "";
|
||||
bool status = false;
|
||||
|
||||
if (object_instance == 0)
|
||||
status = characterstring_init_ansi(object_name, "Led");
|
||||
else {
|
||||
if (object_instance < MAX_BINARY_OUTPUTS) {
|
||||
snprintf(text, sizeof(text), "BINARY OUTPUT %lu",
|
||||
(unsigned long)object_instance);
|
||||
status = characterstring_init_ansi(object_name, text);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/* return apdu len, or BACNET_STATUS_ERROR on error */
|
||||
int Binary_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int len = 0;
|
||||
int apdu_len = 0; /* return value */
|
||||
BACNET_BIT_STRING bit_string;
|
||||
BACNET_CHARACTER_STRING char_string;
|
||||
BACNET_BINARY_PV present_value = BINARY_INACTIVE;
|
||||
BACNET_POLARITY polarity = POLARITY_NORMAL;
|
||||
unsigned object_index = 0;
|
||||
unsigned i = 0;
|
||||
bool state = false;
|
||||
uint8_t *apdu = NULL;
|
||||
|
||||
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
|
||||
(rpdata->application_data_len == 0)) {
|
||||
return 0;
|
||||
}
|
||||
apdu = rpdata->application_data;
|
||||
switch (rpdata->object_property) {
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
apdu_len = encode_application_object_id(
|
||||
&apdu[0], OBJECT_BINARY_OUTPUT, rpdata->object_instance);
|
||||
break;
|
||||
/* note: Name and Description don't have to be the same.
|
||||
You could make Description writable and different */
|
||||
case PROP_OBJECT_NAME:
|
||||
case PROP_DESCRIPTION:
|
||||
Binary_Output_Object_Name(rpdata->object_instance, &char_string);
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_OBJECT_TYPE:
|
||||
apdu_len =
|
||||
encode_application_enumerated(&apdu[0], OBJECT_BINARY_OUTPUT);
|
||||
break;
|
||||
case PROP_PRESENT_VALUE:
|
||||
present_value =
|
||||
Binary_Output_Present_Value(rpdata->object_instance);
|
||||
apdu_len = encode_application_enumerated(&apdu[0], present_value);
|
||||
break;
|
||||
case PROP_STATUS_FLAGS:
|
||||
/* note: see the details in the standard on how to use these */
|
||||
bitstring_init(&bit_string);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
|
||||
bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false);
|
||||
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
|
||||
break;
|
||||
case PROP_EVENT_STATE:
|
||||
/* note: see the details in the standard on how to use this */
|
||||
apdu_len =
|
||||
encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
|
||||
break;
|
||||
case PROP_OUT_OF_SERVICE:
|
||||
object_index =
|
||||
Binary_Output_Instance_To_Index(rpdata->object_instance);
|
||||
state = Out_Of_Service[object_index];
|
||||
apdu_len = encode_application_boolean(&apdu[0], state);
|
||||
break;
|
||||
case PROP_POLARITY:
|
||||
apdu_len = encode_application_enumerated(&apdu[0], polarity);
|
||||
break;
|
||||
case PROP_PRIORITY_ARRAY:
|
||||
/* Array element zero is the number of elements in the array */
|
||||
if (rpdata->array_index == 0)
|
||||
apdu_len =
|
||||
encode_application_unsigned(&apdu[0], BACNET_MAX_PRIORITY);
|
||||
/* if no index was specified, then try to encode the entire list */
|
||||
/* into one packet. */
|
||||
else if (rpdata->array_index == BACNET_ARRAY_ALL) {
|
||||
object_index =
|
||||
Binary_Output_Instance_To_Index(rpdata->object_instance);
|
||||
for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
|
||||
/* FIXME: check if we have room before adding it to APDU */
|
||||
if (Binary_Output_Level[object_index][i] == BINARY_NULL)
|
||||
len = encode_application_null(&apdu[apdu_len]);
|
||||
else {
|
||||
present_value = Binary_Output_Level[object_index][i];
|
||||
len = encode_application_enumerated(
|
||||
&apdu[apdu_len], present_value);
|
||||
}
|
||||
/* add it if we have room */
|
||||
if ((apdu_len + len) < MAX_APDU)
|
||||
apdu_len += len;
|
||||
else {
|
||||
rpdata->error_code =
|
||||
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
|
||||
apdu_len = BACNET_STATUS_ABORT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
object_index =
|
||||
Binary_Output_Instance_To_Index(rpdata->object_instance);
|
||||
if (rpdata->array_index <= BACNET_MAX_PRIORITY) {
|
||||
if (Binary_Output_Level[object_index][rpdata->array_index -
|
||||
1] == BINARY_NULL)
|
||||
apdu_len = encode_application_null(&apdu[apdu_len]);
|
||||
else {
|
||||
present_value =
|
||||
Binary_Output_Level[object_index]
|
||||
[rpdata->array_index - 1];
|
||||
apdu_len = encode_application_enumerated(
|
||||
&apdu[apdu_len], present_value);
|
||||
}
|
||||
} else {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case PROP_RELINQUISH_DEFAULT:
|
||||
present_value = RELINQUISH_DEFAULT;
|
||||
apdu_len = encode_application_enumerated(&apdu[0], present_value);
|
||||
break;
|
||||
case PROP_ACTIVE_TEXT:
|
||||
characterstring_init_ansi(&char_string, "on");
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
case PROP_INACTIVE_TEXT:
|
||||
characterstring_init_ansi(&char_string, "off");
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
break;
|
||||
default:
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
/* only array properties can have array options */
|
||||
if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
|
||||
(rpdata->array_index != BACNET_ARRAY_ALL)) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* returns true if successful */
|
||||
bool Binary_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
unsigned int object_index = 0;
|
||||
unsigned int priority = 0;
|
||||
BACNET_BINARY_PV level = BINARY_NULL;
|
||||
int len = 0;
|
||||
BACNET_APPLICATION_DATA_VALUE value = { 0 };
|
||||
|
||||
/* decode the some of the request */
|
||||
len = bacapp_decode_application_data(
|
||||
wp_data->application_data, wp_data->application_data_len, &value);
|
||||
/* FIXME: len < application_data_len: more data? */
|
||||
if (len < 0) {
|
||||
/* error while decoding - a value larger than we can handle */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
return false;
|
||||
}
|
||||
if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
|
||||
(wp_data->array_index != BACNET_ARRAY_ALL)) {
|
||||
/* only array properties can have array options */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
return false;
|
||||
}
|
||||
switch (wp_data->object_property) {
|
||||
case PROP_PRESENT_VALUE:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
priority = wp_data->priority;
|
||||
/* Command priority 6 is reserved for use by Minimum On/Off
|
||||
algorithm and may not be used for other purposes in any
|
||||
object. */
|
||||
if (priority && (priority <= BACNET_MAX_PRIORITY) &&
|
||||
(priority != 6 /* reserved */) &&
|
||||
(value.type.Enumerated <= MAX_BINARY_PV)) {
|
||||
level = (BACNET_BINARY_PV)value.type.Enumerated;
|
||||
object_index = Binary_Output_Instance_To_Index(
|
||||
wp_data->object_instance);
|
||||
priority--;
|
||||
Binary_Output_Level[object_index][priority] = level;
|
||||
/* Note: you could set the physical output here if we
|
||||
are the highest priority.
|
||||
However, if Out of Service is TRUE, then don't set the
|
||||
physical output. This comment may apply to the
|
||||
main loop (i.e. check out of service before changing
|
||||
output) */
|
||||
status = true;
|
||||
} else if (priority == 6) {
|
||||
/* Command priority 6 is reserved for use by Minimum On/Off
|
||||
algorithm and may not be used for other purposes in any
|
||||
object. */
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
status = write_property_type_valid(wp_data, &value,
|
||||
BACNET_APPLICATION_TAG_NULL);
|
||||
if (status) {
|
||||
level = BINARY_NULL;
|
||||
object_index = Binary_Output_Instance_To_Index(
|
||||
wp_data->object_instance);
|
||||
priority = wp_data->priority;
|
||||
if (priority && (priority <= BACNET_MAX_PRIORITY)) {
|
||||
priority--;
|
||||
Binary_Output_Level[object_index][priority] = level;
|
||||
/* Note: you could set the physical output here to the
|
||||
next highest priority, or to the relinquish default
|
||||
if no priorities are set. However, if Out of Service
|
||||
is TRUE, then don't set the physical output. This
|
||||
comment may apply to the
|
||||
main loop (i.e. check out of service before changing
|
||||
output) */
|
||||
} else {
|
||||
status = false;
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROP_OUT_OF_SERVICE:
|
||||
status = write_property_type_valid(wp_data, &value,
|
||||
BACNET_APPLICATION_TAG_BOOLEAN);
|
||||
if (status) {
|
||||
object_index =
|
||||
Binary_Output_Instance_To_Index(wp_data->object_instance);
|
||||
Out_Of_Service[object_index] = value.type.Boolean;
|
||||
}
|
||||
break;
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
case PROP_OBJECT_NAME:
|
||||
case PROP_OBJECT_TYPE:
|
||||
case PROP_STATUS_FLAGS:
|
||||
case PROP_RELIABILITY:
|
||||
case PROP_EVENT_STATE:
|
||||
case PROP_POLARITY:
|
||||
case PROP_PRIORITY_ARRAY:
|
||||
case PROP_RELINQUISH_DEFAULT:
|
||||
case PROP_ACTIVE_TEXT:
|
||||
case PROP_INACTIVE_TEXT:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
break;
|
||||
default:
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*********************************************************************/
|
||||
#ifndef BO_H
|
||||
#define BO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacerror.h"
|
||||
#include "bacnet/rp.h"
|
||||
#include "bacnet/wp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
void Binary_Output_Init(
|
||||
void);
|
||||
|
||||
void Binary_Output_Property_Lists(
|
||||
const int32_t **pRequired,
|
||||
const int32_t **pOptional,
|
||||
const int32_t **pProprietary);
|
||||
|
||||
bool Binary_Output_Valid_Instance(
|
||||
uint32_t object_instance);
|
||||
unsigned Binary_Output_Count(
|
||||
void);
|
||||
uint32_t Binary_Output_Index_To_Instance(
|
||||
unsigned index);
|
||||
unsigned Binary_Output_Instance_To_Index(
|
||||
uint32_t instance);
|
||||
bool Binary_Output_Object_Instance_Add(
|
||||
uint32_t instance);
|
||||
|
||||
bool Binary_Output_Object_Name(
|
||||
uint32_t object_instance,
|
||||
BACNET_CHARACTER_STRING * object_name);
|
||||
bool Binary_Output_Name_Set(
|
||||
uint32_t object_instance,
|
||||
char *new_name);
|
||||
|
||||
const char *Binary_Output_Description(
|
||||
uint32_t instance);
|
||||
bool Binary_Output_Description_Set(
|
||||
uint32_t instance,
|
||||
const char *new_name);
|
||||
|
||||
const char *Binary_Output_Inactive_Text(
|
||||
uint32_t instance);
|
||||
bool Binary_Output_Inactive_Text_Set(
|
||||
uint32_t instance,
|
||||
char *new_name);
|
||||
char *Binary_Output_Active_Text(
|
||||
uint32_t instance);
|
||||
bool Binary_Output_Active_Text_Set(
|
||||
uint32_t instance,
|
||||
char *new_name);
|
||||
|
||||
BACNET_BINARY_PV Binary_Output_Present_Value(
|
||||
uint32_t instance);
|
||||
bool Binary_Output_Present_Value_Set(
|
||||
uint32_t instance,
|
||||
BACNET_BINARY_PV binary_value,
|
||||
unsigned priority);
|
||||
bool Binary_Output_Present_Value_Relinquish(
|
||||
uint32_t instance,
|
||||
unsigned priority);
|
||||
unsigned Binary_Output_Present_Value_Priority(
|
||||
uint32_t object_instance);
|
||||
|
||||
BACNET_POLARITY Binary_Output_Polarity(
|
||||
uint32_t instance);
|
||||
bool Binary_Output_Polarity_Set(
|
||||
uint32_t object_instance,
|
||||
BACNET_POLARITY polarity);
|
||||
|
||||
bool Binary_Output_Out_Of_Service(
|
||||
uint32_t instance);
|
||||
void Binary_Output_Out_Of_Service_Set(
|
||||
uint32_t object_instance,
|
||||
bool value);
|
||||
|
||||
BACNET_BINARY_PV Binary_Output_Relinquish_Default(
|
||||
uint32_t object_instance);
|
||||
bool Binary_Output_Relinquish_Default_Set(
|
||||
uint32_t object_instance,
|
||||
BACNET_BINARY_PV value);
|
||||
|
||||
bool Binary_Output_Encode_Value_List(
|
||||
uint32_t object_instance,
|
||||
BACNET_PROPERTY_VALUE * value_list);
|
||||
bool Binary_Output_Change_Of_Value(
|
||||
uint32_t instance);
|
||||
void Binary_Output_Change_Of_Value_Clear(
|
||||
uint32_t instance);
|
||||
|
||||
int Binary_Output_Read_Property(
|
||||
BACNET_READ_PROPERTY_DATA * rpdata);
|
||||
bool Binary_Output_Write_Property(
|
||||
BACNET_WRITE_PROPERTY_DATA * wp_data);
|
||||
|
||||
uint32_t Binary_Output_Create(
|
||||
uint32_t object_instance);
|
||||
bool Binary_Output_Delete(
|
||||
uint32_t object_instance);
|
||||
void Binary_Output_Cleanup(
|
||||
void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet Virtual Link Control implementation for the PlatformIO ESP32
|
||||
* port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bip.h"
|
||||
#include "bvlc.h"
|
||||
#include "bacnet/bacint.h"
|
||||
|
||||
#define BVLC_RESULT 0
|
||||
#define BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE 1
|
||||
#define BVLC_READ_BROADCAST_DIST_TABLE 2
|
||||
#define BVLC_READ_BROADCAST_DIST_TABLE_ACK 3
|
||||
#define BVLC_FORWARDED_NPDU 4
|
||||
#define BVLC_REGISTER_FOREIGN_DEVICE 5
|
||||
#define BVLC_READ_FOREIGN_DEVICE_TABLE 6
|
||||
#define BVLC_READ_FOREIGN_DEVICE_TABLE_ACK 7
|
||||
#define BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY 8
|
||||
#define BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK 9
|
||||
#define BVLC_ORIGINAL_UNICAST_NPDU 10
|
||||
#define BVLC_ORIGINAL_BROADCAST_NPDU 11
|
||||
#define BVLC_SECURE_BVLL 12
|
||||
|
||||
#define BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0010
|
||||
#define BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0020
|
||||
#define BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK 0X0030
|
||||
#define BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK 0x0040
|
||||
#define BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK 0x0050
|
||||
#define BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK 0x0060
|
||||
|
||||
static BACNET_BVLC_RESULT BVLC_Result_Code = BVLC_RESULT_SUCCESSFUL_COMPLETION;
|
||||
static BACNET_BVLC_FUNCTION BVLC_Function_Code = BVLC_RESULT;
|
||||
|
||||
/**
|
||||
* @brief Encode a BVLC result message
|
||||
* @param pdu destination buffer, or NULL for length-only use
|
||||
* @param result_code BVLC result code to encode
|
||||
* @return encoded BVLC message length
|
||||
*/
|
||||
static int bvlc_encode_bvlc_result(uint8_t *pdu, BACNET_BVLC_RESULT result_code)
|
||||
{
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_RESULT;
|
||||
encode_unsigned16(&pdu[2], 6);
|
||||
encode_unsigned16(&pdu[4], (uint16_t)result_code);
|
||||
}
|
||||
|
||||
return 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a raw BVLC frame to the destination host
|
||||
* @param dest_addr destination IPv4 address
|
||||
* @param dest_port destination UDP port
|
||||
* @param mtu raw BVLC frame buffer
|
||||
* @param mtu_len frame length in bytes
|
||||
* @return number of bytes sent, or -1 on error
|
||||
*/
|
||||
static int bvlc_send_mpdu(
|
||||
const uint8_t *dest_addr,
|
||||
const uint16_t *dest_port,
|
||||
uint8_t *mtu,
|
||||
uint16_t mtu_len)
|
||||
{
|
||||
int bytes_sent = 0;
|
||||
|
||||
if (!bip_valid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bytes_sent = bip_socket_send(dest_addr, *dest_port, mtu, mtu_len);
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a BVLC result response
|
||||
* @param dest_addr destination IPv4 address
|
||||
* @param dest_port destination UDP port
|
||||
* @param result_code BVLC result code to report
|
||||
*/
|
||||
static void bvlc_send_result(
|
||||
const uint8_t *dest_addr,
|
||||
const uint16_t *dest_port,
|
||||
BACNET_BVLC_RESULT result_code)
|
||||
{
|
||||
uint8_t mtu[BIP_MPDU_MAX] = { 0 };
|
||||
uint16_t mtu_len = 0;
|
||||
|
||||
mtu_len = (uint16_t)bvlc_encode_bvlc_result(&mtu[0], result_code);
|
||||
bvlc_send_mpdu(dest_addr, dest_port, mtu, mtu_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle non-BBMD BVLC functions for this port
|
||||
* @param addr source IPv4 address
|
||||
* @param port source UDP port
|
||||
* @param npdu received BVLC frame buffer
|
||||
* @param received_bytes frame length in bytes
|
||||
* @return non-zero when the frame was consumed by BVLC handling
|
||||
*/
|
||||
uint16_t bvlc_for_non_bbmd(
|
||||
uint8_t *addr, uint16_t *port, uint8_t *npdu, uint16_t received_bytes)
|
||||
{
|
||||
uint16_t result_code = 0;
|
||||
|
||||
if (received_bytes >= 1) {
|
||||
BVLC_Function_Code = npdu[1];
|
||||
switch (BVLC_Function_Code) {
|
||||
case BVLC_RESULT:
|
||||
if (received_bytes >= 6) {
|
||||
(void)decode_unsigned16(&npdu[4], &result_code);
|
||||
BVLC_Result_Code = (BACNET_BVLC_RESULT)result_code;
|
||||
fprintf(stderr, "BVLC: Result Code=%d\n", BVLC_Result_Code);
|
||||
result_code = 0;
|
||||
}
|
||||
break;
|
||||
case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE:
|
||||
result_code =
|
||||
BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK;
|
||||
break;
|
||||
case BVLC_READ_BROADCAST_DIST_TABLE:
|
||||
result_code = BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK;
|
||||
break;
|
||||
case BVLC_REGISTER_FOREIGN_DEVICE:
|
||||
result_code = BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK;
|
||||
break;
|
||||
case BVLC_READ_FOREIGN_DEVICE_TABLE:
|
||||
result_code = BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK;
|
||||
break;
|
||||
case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY:
|
||||
result_code = BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK;
|
||||
break;
|
||||
case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK:
|
||||
result_code = BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result_code > 0) {
|
||||
bvlc_send_result(addr, port, result_code);
|
||||
fprintf(stderr, "BVLC: NAK code=%d\n", result_code);
|
||||
}
|
||||
|
||||
return result_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the last decoded BVLC function code
|
||||
* @return BVLC function code
|
||||
*/
|
||||
BACNET_BVLC_FUNCTION pico_bvlc_get_function_code(void)
|
||||
{
|
||||
return BVLC_Function_Code;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet Virtual Link Control interfaces for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#ifndef M5STAMPLC_BVLC_H
|
||||
#define M5STAMPLC_BVLC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bacnet/datalink/bvlc.h"
|
||||
|
||||
#define BACNET_BVLC_RESULT uint8_t
|
||||
#define BACNET_BVLC_FUNCTION uint8_t
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef BIP_MPDU_MAX
|
||||
#define BIP_MPDU_MAX 1506
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Handle non-BBMD BVLC functions for a received frame
|
||||
* @param addr source IPv4 address
|
||||
* @param port source UDP port
|
||||
* @param npdu received frame buffer
|
||||
* @param received_bytes frame length in bytes
|
||||
* @return non-zero when the frame was consumed by BVLC handling
|
||||
*/
|
||||
uint16_t bvlc_for_non_bbmd(
|
||||
uint8_t *addr, uint16_t *port, uint8_t *npdu, uint16_t received_bytes);
|
||||
|
||||
/**
|
||||
* @brief Get the most recently decoded BVLC function code
|
||||
* @return BVLC function code
|
||||
*/
|
||||
BACNET_BVLC_FUNCTION pico_bvlc_get_function_code(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,461 +0,0 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
/** @file device.h Defines functions for handling all BACnet objects belonging
|
||||
* to a BACnet device, as well as Device-specific properties. */
|
||||
|
||||
#ifndef DEVICE_H
|
||||
#define DEVICE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
#include "bacnet/wp.h"
|
||||
#include "bacnet/rd.h"
|
||||
#include "bacnet/rp.h"
|
||||
#include "bacnet/rpm.h"
|
||||
#include "bacnet/readrange.h"
|
||||
|
||||
/** Called so a BACnet object can perform any necessary initialization.
|
||||
* @ingroup ObjHelpers
|
||||
*/
|
||||
typedef void (
|
||||
*object_init_function) (
|
||||
void);
|
||||
|
||||
/** Counts the number of objects of this type.
|
||||
* @ingroup ObjHelpers
|
||||
* @return Count of implemented objects of this type.
|
||||
*/
|
||||
typedef unsigned (
|
||||
*object_count_function) (
|
||||
void);
|
||||
|
||||
/** Maps an object index position to its corresponding BACnet object instance number.
|
||||
* @ingroup ObjHelpers
|
||||
* @param index [in] The index of the object, in the array of objects of its type.
|
||||
* @return The BACnet object instance number to be used in a BACNET_OBJECT_ID.
|
||||
*/
|
||||
typedef uint32_t(
|
||||
*object_index_to_instance_function)
|
||||
(
|
||||
unsigned index);
|
||||
|
||||
/** Provides the BACnet Object_Name for a given object instance of this type.
|
||||
* @ingroup ObjHelpers
|
||||
* @param object_instance [in] The object instance number to be looked up.
|
||||
* @param object_name [in,out] Pointer to a character_string structure that
|
||||
* will hold a copy of the object name if this is a valid object_instance.
|
||||
* @return True if the object_instance is valid and object_name has been
|
||||
* filled with a copy of the Object's name.
|
||||
*/
|
||||
typedef bool(
|
||||
*object_name_function)
|
||||
(
|
||||
uint32_t object_instance,
|
||||
BACNET_CHARACTER_STRING * object_name);
|
||||
|
||||
/** Look in the table of objects of this type, and see if this is a valid
|
||||
* instance number.
|
||||
* @ingroup ObjHelpers
|
||||
* @param [in] The object instance number to be looked up.
|
||||
* @return True if the object instance refers to a valid object of this type.
|
||||
*/
|
||||
typedef bool(
|
||||
*object_valid_instance_function) (
|
||||
uint32_t object_instance);
|
||||
|
||||
/** Helper function to step through an array of objects and find either the
|
||||
* first one or the next one of a given type. Used to step through an array
|
||||
* of objects which is not necessarily contiguious for each type i.e. the
|
||||
* index for the 'n'th object of a given type is not necessarily 'n'.
|
||||
* @ingroup ObjHelpers
|
||||
* @param [in] The index of the current object or a value of ~0 to indicate
|
||||
* start at the beginning.
|
||||
* @return The index of the next object of the required type or ~0 (all bits
|
||||
* == 1) to indicate no more objects found.
|
||||
*/
|
||||
typedef unsigned (
|
||||
*object_iterate_function) (
|
||||
unsigned current_index);
|
||||
|
||||
/** Look in the table of objects of this type, and get the COV Value List.
|
||||
* @ingroup ObjHelpers
|
||||
* @param [in] The object instance number to be looked up.
|
||||
* @param [out] The value list
|
||||
* @return True if the object instance supports this feature, and has changed.
|
||||
*/
|
||||
typedef bool(
|
||||
*object_value_list_function) (
|
||||
uint32_t object_instance,
|
||||
BACNET_PROPERTY_VALUE * value_list);
|
||||
|
||||
/** Look in the table of objects for this instance to see if value changed.
|
||||
* @ingroup ObjHelpers
|
||||
* @param [in] The object instance number to be looked up.
|
||||
* @return True if the object instance has changed.
|
||||
*/
|
||||
typedef bool(
|
||||
*object_cov_function) (
|
||||
uint32_t object_instance);
|
||||
|
||||
/** Look in the table of objects for this instance to clear the changed flag.
|
||||
* @ingroup ObjHelpers
|
||||
* @param [in] The object instance number to be looked up.
|
||||
*/
|
||||
typedef void (
|
||||
*object_cov_clear_function) (
|
||||
uint32_t object_instance);
|
||||
|
||||
/** Intrinsic Reporting funcionality.
|
||||
* @ingroup ObjHelpers
|
||||
* @param [in] Object instance.
|
||||
*/
|
||||
typedef void (
|
||||
*object_intrinsic_reporting_function) (
|
||||
uint32_t object_instance);
|
||||
|
||||
|
||||
/** Defines the group of object helper functions for any supported Object.
|
||||
* @ingroup ObjHelpers
|
||||
* Each Object must provide some implementation of each of these helpers
|
||||
* in order to properly support the handlers. Eg, the ReadProperty handler
|
||||
* handler_read_property() relies on the instance of Object_Read_Property
|
||||
* for each Object type, or configure the function as NULL.
|
||||
* In both appearance and operation, this group of functions acts like
|
||||
* they are member functions of a C++ Object base class.
|
||||
*/
|
||||
typedef struct object_functions {
|
||||
BACNET_OBJECT_TYPE Object_Type;
|
||||
object_init_function Object_Init;
|
||||
object_count_function Object_Count;
|
||||
object_index_to_instance_function Object_Index_To_Instance;
|
||||
object_valid_instance_function Object_Valid_Instance;
|
||||
object_name_function Object_Name;
|
||||
read_property_function Object_Read_Property;
|
||||
write_property_function Object_Write_Property;
|
||||
rpm_property_lists_function Object_RPM_List;
|
||||
rr_info_function Object_RR_Info;
|
||||
object_iterate_function Object_Iterator;
|
||||
object_value_list_function Object_Value_List;
|
||||
object_cov_function Object_COV;
|
||||
object_cov_clear_function Object_COV_Clear;
|
||||
object_intrinsic_reporting_function Object_Intrinsic_Reporting;
|
||||
} object_functions_t;
|
||||
|
||||
/* String Lengths - excluding any nul terminator */
|
||||
#define MAX_DEV_NAME_LEN 32
|
||||
#define MAX_DEV_LOC_LEN 64
|
||||
#define MAX_DEV_MOD_LEN 32
|
||||
#define MAX_DEV_VER_LEN 16
|
||||
#define MAX_DEV_DESC_LEN 64
|
||||
|
||||
/** Structure to define the Object Properties common to all Objects. */
|
||||
typedef struct commonBacObj_s {
|
||||
|
||||
/** The BACnet type of this object (ie, what class is this object from?).
|
||||
* This property, of type BACnetObjectType, indicates membership in a
|
||||
* particular object type class. Each inherited class will be of one type.
|
||||
*/
|
||||
BACNET_OBJECT_TYPE mObject_Type;
|
||||
|
||||
/** The instance number for this class instance. */
|
||||
uint32_t Object_Instance_Number;
|
||||
|
||||
/** Object Name; must be unique.
|
||||
* This property, of type CharacterString, shall represent a name for
|
||||
* the object that is unique within the BACnet Device that maintains it.
|
||||
*/
|
||||
char Object_Name[MAX_DEV_NAME_LEN];
|
||||
|
||||
} COMMON_BAC_OBJECT;
|
||||
|
||||
|
||||
/** Structure to define the Properties of Device Objects which distinguish
|
||||
* one instance from another.
|
||||
* This structure only defines fields for properties that are unique to
|
||||
* a given Device object. The rest may be fixed in device.c or hard-coded
|
||||
* into the read-property encoding.
|
||||
* This may be useful for implementations which manage multiple Devices,
|
||||
* eg, a Gateway.
|
||||
*/
|
||||
typedef struct devObj_s {
|
||||
/** The BACnet Device Address for this device; ->len depends on DLL type. */
|
||||
BACNET_ADDRESS bacDevAddr;
|
||||
|
||||
/** Structure for the Object Properties common to all Objects. */
|
||||
COMMON_BAC_OBJECT bacObj;
|
||||
|
||||
/** Device Description. */
|
||||
char Description[MAX_DEV_DESC_LEN];
|
||||
|
||||
/** The upcounter that shows if the Device ID or object structure has changed. */
|
||||
uint32_t Database_Revision;
|
||||
} DEVICE_OBJECT_DATA;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
void Device_Init(
|
||||
object_functions_t * object_table);
|
||||
|
||||
bool Device_Reinitialize(
|
||||
BACNET_REINITIALIZE_DEVICE_DATA * rd_data);
|
||||
bool Device_Reinitialize_State_Set(BACNET_REINITIALIZED_STATE state);
|
||||
BACNET_REINITIALIZED_STATE Device_Reinitialized_State(
|
||||
void);
|
||||
|
||||
rr_info_function Device_Objects_RR_Info(
|
||||
BACNET_OBJECT_TYPE object_type);
|
||||
|
||||
void Device_getCurrentDateTime(
|
||||
BACNET_DATE_TIME * DateTime);
|
||||
|
||||
int32_t Device_UTC_Offset(void);
|
||||
void Device_UTC_Offset_Set(int16_t offset);
|
||||
|
||||
bool Device_Daylight_Savings_Status(void);
|
||||
bool Device_Align_Intervals(void);
|
||||
bool Device_Align_Intervals_Set(bool flag);
|
||||
uint32_t Device_Time_Sync_Interval(void);
|
||||
bool Device_Time_Sync_Interval_Set(uint32_t value);
|
||||
uint32_t Device_Interval_Offset(void);
|
||||
bool Device_Interval_Offset_Set(uint32_t value);
|
||||
|
||||
void Device_Property_Lists(
|
||||
const int32_t **pRequired,
|
||||
const int32_t **pOptional,
|
||||
const int32_t **pProprietary);
|
||||
void Device_Objects_Property_List(
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance,
|
||||
struct special_property_list_t *pPropertyList);
|
||||
/* functions to support COV */
|
||||
bool Device_Encode_Value_List(
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance,
|
||||
BACNET_PROPERTY_VALUE * value_list);
|
||||
bool Device_Value_List_Supported(
|
||||
BACNET_OBJECT_TYPE object_type);
|
||||
bool Device_COV(
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance);
|
||||
void Device_COV_Clear(
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance);
|
||||
|
||||
uint32_t Device_Object_Instance_Number(
|
||||
void);
|
||||
bool Device_Set_Object_Instance_Number(
|
||||
uint32_t object_id);
|
||||
bool Device_Valid_Object_Instance_Number(
|
||||
uint32_t object_id);
|
||||
unsigned Device_Object_List_Count(
|
||||
void);
|
||||
bool Device_Object_List_Identifier(
|
||||
uint32_t array_index,
|
||||
BACNET_OBJECT_TYPE *object_type,
|
||||
uint32_t * instance);
|
||||
|
||||
unsigned Device_Count(
|
||||
void);
|
||||
uint32_t Device_Index_To_Instance(
|
||||
unsigned index);
|
||||
|
||||
bool Device_Object_Name(
|
||||
uint32_t object_instance,
|
||||
BACNET_CHARACTER_STRING * object_name);
|
||||
bool Device_Set_Object_Name(
|
||||
const BACNET_CHARACTER_STRING * object_name);
|
||||
/* Copy a child object name, given its ID. */
|
||||
bool Device_Object_Name_Copy(
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance,
|
||||
BACNET_CHARACTER_STRING * object_name);
|
||||
bool Device_Object_Name_ANSI_Init(const char * object_name);
|
||||
char * Device_Object_Name_ANSI(void);
|
||||
|
||||
BACNET_DEVICE_STATUS Device_System_Status(
|
||||
void);
|
||||
int Device_Set_System_Status(
|
||||
BACNET_DEVICE_STATUS status,
|
||||
bool local);
|
||||
|
||||
const char *Device_Vendor_Name(
|
||||
void);
|
||||
|
||||
uint16_t Device_Vendor_Identifier(
|
||||
void);
|
||||
void Device_Set_Vendor_Identifier(
|
||||
uint16_t vendor_id);
|
||||
|
||||
const char *Device_Model_Name(
|
||||
void);
|
||||
bool Device_Set_Model_Name(
|
||||
const char *name,
|
||||
size_t length);
|
||||
|
||||
const char *Device_Firmware_Revision(
|
||||
void);
|
||||
|
||||
const char *Device_Application_Software_Version(
|
||||
void);
|
||||
bool Device_Set_Application_Software_Version(
|
||||
const char *name,
|
||||
size_t length);
|
||||
|
||||
const char *Device_Description(
|
||||
void);
|
||||
bool Device_Set_Description(
|
||||
const char *name,
|
||||
size_t length);
|
||||
|
||||
const char *Device_Location(
|
||||
void);
|
||||
bool Device_Set_Location(
|
||||
const char *name,
|
||||
size_t length);
|
||||
|
||||
/* some stack-centric constant values - no set methods */
|
||||
uint8_t Device_Protocol_Version(
|
||||
void);
|
||||
uint8_t Device_Protocol_Revision(
|
||||
void);
|
||||
BACNET_SEGMENTATION Device_Segmentation_Supported(
|
||||
void);
|
||||
|
||||
uint32_t Device_Database_Revision(
|
||||
void);
|
||||
void Device_Set_Database_Revision(
|
||||
uint32_t revision);
|
||||
void Device_Inc_Database_Revision(
|
||||
void);
|
||||
|
||||
bool Device_Valid_Object_Name(
|
||||
const BACNET_CHARACTER_STRING * object_name,
|
||||
BACNET_OBJECT_TYPE *object_type,
|
||||
uint32_t * object_instance);
|
||||
bool Device_Valid_Object_Id(
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance);
|
||||
|
||||
int Device_Read_Property(
|
||||
BACNET_READ_PROPERTY_DATA * rpdata);
|
||||
bool Device_Write_Property(
|
||||
BACNET_WRITE_PROPERTY_DATA * wp_data);
|
||||
|
||||
bool DeviceGetRRInfo(
|
||||
BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */
|
||||
RR_PROP_INFO * pInfo); /* Where to put the information */
|
||||
|
||||
int Device_Read_Property_Local(
|
||||
BACNET_READ_PROPERTY_DATA * rpdata);
|
||||
bool Device_Write_Property_Local(
|
||||
BACNET_WRITE_PROPERTY_DATA * wp_data);
|
||||
|
||||
#if defined(INTRINSIC_REPORTING)
|
||||
void Device_local_reporting(
|
||||
void);
|
||||
#endif
|
||||
|
||||
/* Prototypes for Routing functionality in the Device Object.
|
||||
* Enable by defining BAC_ROUTING in config.h and including gw_device.c
|
||||
* in the build (lib/Makefile).
|
||||
*/
|
||||
void Routing_Device_Init(
|
||||
uint32_t first_object_instance);
|
||||
|
||||
uint16_t Add_Routed_Device(
|
||||
uint32_t Object_Instance,
|
||||
const BACNET_CHARACTER_STRING * Object_Name,
|
||||
const char *Description);
|
||||
DEVICE_OBJECT_DATA *Get_Routed_Device_Object(
|
||||
int idx);
|
||||
BACNET_ADDRESS *Get_Routed_Device_Address(
|
||||
int idx);
|
||||
|
||||
void routed_get_my_address(
|
||||
BACNET_ADDRESS * my_address);
|
||||
|
||||
bool Routed_Device_Address_Lookup(
|
||||
int idx,
|
||||
uint8_t address_len,
|
||||
const uint8_t * mac_adress);
|
||||
bool Routed_Device_GetNext(
|
||||
const BACNET_ADDRESS * dest,
|
||||
const int32_t *DNET_list,
|
||||
int *cursor);
|
||||
bool Routed_Device_Is_Valid_Network(
|
||||
uint16_t dest_net,
|
||||
const int32_t *DNET_list);
|
||||
|
||||
uint32_t Routed_Device_Index_To_Instance(
|
||||
unsigned index);
|
||||
bool Routed_Device_Valid_Object_Instance_Number(
|
||||
uint32_t object_id);
|
||||
bool Routed_Device_Name(
|
||||
uint32_t object_instance,
|
||||
BACNET_CHARACTER_STRING * object_name);
|
||||
uint32_t Routed_Device_Object_Instance_Number(
|
||||
void);
|
||||
bool Routed_Device_Set_Object_Instance_Number(
|
||||
uint32_t object_id);
|
||||
bool Routed_Device_Set_Object_Name(
|
||||
uint8_t encoding,
|
||||
const char *value,
|
||||
size_t length);
|
||||
bool Routed_Device_Set_Description(
|
||||
const char *name,
|
||||
size_t length);
|
||||
void Routed_Device_Inc_Database_Revision(
|
||||
void);
|
||||
int Routed_Device_Service_Approval(
|
||||
BACNET_CONFIRMED_SERVICE service,
|
||||
int service_argument,
|
||||
uint8_t * apdu_buff,
|
||||
uint8_t invoke_id);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
/** @defgroup ObjFrmwk Object Framework
|
||||
* The modules in this section describe the BACnet-stack's framework for
|
||||
* BACnet-defined Objects (Device, Analog Input, etc). There are two submodules
|
||||
* to describe this arrangement:
|
||||
* - The "object helper functions" which provide C++-like common functionality
|
||||
* to all supported object types.
|
||||
* - The interface between the implemented Objects and the BAC-stack services,
|
||||
* specifically the handlers, which are mediated through function calls to
|
||||
* the Device object.
|
||||
*/
|
||||
/** @defgroup ObjHelpers Object Helper Functions
|
||||
* @ingroup ObjFrmwk
|
||||
* This section describes the function templates for the helper functions that
|
||||
* provide common object support.
|
||||
*/
|
||||
/** @defgroup ObjIntf Handler-to-Object Interface Functions
|
||||
* @ingroup ObjFrmwk
|
||||
* This section describes the fairly limited set of functions that link the
|
||||
* BAC-stack handlers to the BACnet Object instances. All of these calls are
|
||||
* situated in the Device Object, which "knows" how to reach its child Objects.
|
||||
*
|
||||
* Most of these calls have a common operation:
|
||||
* -# Call Device_Objects_Find_Functions( for the desired Object_Type )
|
||||
* - Gets a pointer to the object_functions for this Type of Object.
|
||||
* -# Call the Object's Object_Valid_Instance( for the desired object_instance )
|
||||
* to make sure there is such an instance.
|
||||
* -# Call the Object helper function needed by the handler,
|
||||
* eg Object_Read_Property() for the RP handler.
|
||||
*
|
||||
*/
|
||||
#endif
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet MS/TP datalink environment setup for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include "dlenv.h"
|
||||
|
||||
#include "bacnet/datalink/dlmstp.h"
|
||||
#include "rs485.h"
|
||||
|
||||
volatile struct mstp_port_struct_t MSTP_Port;
|
||||
|
||||
static struct dlmstp_user_data_t MSTP_User_Data;
|
||||
static uint8_t Input_Buffer[DLMSTP_MPDU_MAX];
|
||||
static uint8_t Output_Buffer[DLMSTP_MPDU_MAX];
|
||||
|
||||
static struct dlmstp_rs485_driver RS485_Driver = {
|
||||
.init = rs485_init,
|
||||
.send = rs485_bytes_send,
|
||||
.read = rs485_byte_available,
|
||||
.transmitting = rs485_rts_enabled,
|
||||
.baud_rate = rs485_baud_rate,
|
||||
.baud_rate_set = rs485_baud_rate_set,
|
||||
.silence_milliseconds = rs485_silence_milliseconds,
|
||||
.silence_reset = rs485_silence_reset
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize the BACnet MS/TP datalink environment
|
||||
* @param mac_address local MS/TP MAC address
|
||||
* @return true if the datalink initialized successfully
|
||||
*/
|
||||
bool m5_dlenv_init(uint8_t mac_address)
|
||||
{
|
||||
RS485_Driver.init();
|
||||
|
||||
MSTP_Port.Nmax_info_frames = BACNET_MSTP_MAX_INFO_FRAMES;
|
||||
MSTP_Port.Nmax_master = BACNET_MSTP_MAX_MASTER;
|
||||
|
||||
MSTP_Port.InputBuffer = Input_Buffer;
|
||||
MSTP_Port.InputBufferSize = sizeof(Input_Buffer);
|
||||
MSTP_Port.OutputBuffer = Output_Buffer;
|
||||
MSTP_Port.OutputBufferSize = sizeof(Output_Buffer);
|
||||
|
||||
MSTP_Port.ZeroConfigEnabled = false;
|
||||
MSTP_Port.SlaveNodeEnabled = false;
|
||||
MSTP_Port.CheckAutoBaud = false;
|
||||
|
||||
MSTP_Zero_Config_UUID_Init((struct mstp_port_struct_t *)&MSTP_Port);
|
||||
|
||||
MSTP_User_Data.RS485_Driver = &RS485_Driver;
|
||||
MSTP_Port.UserData = &MSTP_User_Data;
|
||||
|
||||
if (!dlmstp_init((char *)&MSTP_Port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dlmstp_set_mac_address(mac_address);
|
||||
dlmstp_set_baud_rate(RS485_BAUD_RATE);
|
||||
dlmstp_set_max_master(BACNET_MSTP_MAX_MASTER);
|
||||
dlmstp_set_max_info_frames(BACNET_MSTP_MAX_INFO_FRAMES);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run any periodic MS/TP maintenance hooks
|
||||
* @param seconds elapsed seconds since the last call
|
||||
*/
|
||||
void dlenv_maintenance_timer(uint16_t seconds)
|
||||
{
|
||||
(void)seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shut down the MS/TP datalink environment
|
||||
*/
|
||||
void dlenv_cleanup(void)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet MS/TP datalink environment declarations for the PlatformIO
|
||||
* ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#ifndef M5STAMPLC_DLENV_H
|
||||
#define M5STAMPLC_DLENV_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/datalink/dlenv.h"
|
||||
#include "bacnet/datalink/dlmstp.h"
|
||||
#include "bacnet/datalink/mstp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BACNET_MSTP_MAX_MASTER 127
|
||||
#define BACNET_MSTP_MAX_INFO_FRAMES 1
|
||||
|
||||
/**
|
||||
* @brief Initialize the BACnet MS/TP datalink environment
|
||||
* @param mac_address local MS/TP MAC address
|
||||
* @return true if initialization succeeded
|
||||
*/
|
||||
bool m5_dlenv_init(uint8_t mac_address);
|
||||
|
||||
/**
|
||||
* @brief Run periodic MS/TP maintenance work
|
||||
* @param seconds elapsed seconds since the last call
|
||||
*/
|
||||
void dlenv_maintenance_timer(uint16_t seconds);
|
||||
|
||||
/**
|
||||
* @brief Shut down the MS/TP datalink environment
|
||||
*/
|
||||
void dlenv_cleanup(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,218 +0,0 @@
|
||||
//
|
||||
// Copyleft F.Chaxel 2017
|
||||
//
|
||||
|
||||
#include "bacnet/config.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/datalink/datalink.h"
|
||||
#include "bacnet/dcc.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
// conflict filename address.h with another file in default include paths
|
||||
#include "../lib/stack/address.h"
|
||||
#include "bacnet/datalink/bip.h"
|
||||
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/object/ai.h"
|
||||
#include "bacnet/basic/object/bo.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event_loop.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/netdb.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
// hidden function not in any .h files
|
||||
extern uint8_t temprature_sens_read();
|
||||
extern uint32_t hall_sens_read();
|
||||
|
||||
// Wifi params
|
||||
wifi_config_t wifi_config = {
|
||||
.sta = {
|
||||
.ssid = "myWifi",
|
||||
.password = "myPass",
|
||||
},
|
||||
};
|
||||
|
||||
// GPIO 5 has a Led on Sparkfun ESP32 board
|
||||
#define BACNET_LED 5
|
||||
|
||||
uint8_t Handler_Transmit_Buffer[MAX_PDU] = { 0 };
|
||||
|
||||
/** Static receive buffer, initialized with zeros by the C Library Startup Code. */
|
||||
|
||||
uint8_t Rx_Buf[MAX_MPDU + 16 /* Add a little safety margin to the buffer,
|
||||
* so that in the rare case, the message
|
||||
* would be filled up to MAX_MPDU and some
|
||||
* decoding functions would overrun, these
|
||||
* decoding functions will just end up in
|
||||
* a safe field of static zeros. */] = { 0 };
|
||||
|
||||
EventGroupHandle_t wifi_event_group;
|
||||
const static int CONNECTED_BIT = BIT0;
|
||||
|
||||
/* BACnet handler, stack init, IAm */
|
||||
void StartBACnet()
|
||||
{
|
||||
/* we need to handle who-is to support dynamic device binding */
|
||||
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
|
||||
|
||||
/* set the handler for all the services we don't implement */
|
||||
/* It is required to send the proper reject message... */
|
||||
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
|
||||
/* Set the handlers for any confirmed services that we support. */
|
||||
/* We must implement read property - it's required! */
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple);
|
||||
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property);
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_SUBSCRIBE_COV, handler_cov_subscribe);
|
||||
|
||||
address_init();
|
||||
bip_init(NULL);
|
||||
Send_I_Am(&Handler_Transmit_Buffer[0]);
|
||||
}
|
||||
|
||||
/* wifi events handler : start & stop bacnet with an event */
|
||||
esp_err_t wifi_event_handler(void *ctx, system_event_t *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case SYSTEM_EVENT_STA_START:
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_CONNECTED:
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
if (xEventGroupGetBits(wifi_event_group) != CONNECTED_BIT) {
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
StartBACnet();
|
||||
}
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
/* This is a workaround as ESP32 WiFi libs don't currently
|
||||
auto-reassociate. */
|
||||
esp_wifi_connect();
|
||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||
bip_cleanup();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* tcpip & wifi station start */
|
||||
|
||||
void wifi_init_station(void)
|
||||
{
|
||||
tcpip_adapter_init();
|
||||
wifi_event_group = xEventGroupCreate();
|
||||
esp_event_loop_init(wifi_event_handler, NULL);
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
esp_wifi_init(&cfg);
|
||||
|
||||
esp_wifi_set_storage(WIFI_STORAGE_RAM);
|
||||
esp_wifi_set_mode(WIFI_MODE_STA);
|
||||
|
||||
esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
|
||||
|
||||
esp_wifi_start();
|
||||
}
|
||||
|
||||
/* setup gpio & nv flash, call wifi init code */
|
||||
void setup()
|
||||
{
|
||||
gpio_pad_select_gpio(BACNET_LED);
|
||||
gpio_set_direction(BACNET_LED, GPIO_MODE_OUTPUT);
|
||||
|
||||
gpio_set_level(BACNET_LED, 0);
|
||||
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
|
||||
nvs_flash_erase();
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
wifi_init_station();
|
||||
}
|
||||
|
||||
/* Bacnet Task */
|
||||
void BACnetTask(void *pvParameters)
|
||||
{
|
||||
uint16_t pdu_len = 0;
|
||||
BACNET_ADDRESS src = { 0 };
|
||||
unsigned timeout = 1;
|
||||
|
||||
// Init Bacnet objets dictionnary
|
||||
Device_Init(NULL);
|
||||
Device_Set_Object_Instance_Number(12);
|
||||
|
||||
setup();
|
||||
|
||||
uint32_t tickcount = xTaskGetTickCount();
|
||||
|
||||
for (;;) {
|
||||
vTaskDelay(
|
||||
10 / portTICK_PERIOD_MS); // could be remove to speed the code
|
||||
|
||||
// do nothing if not connected to wifi
|
||||
xEventGroupWaitBits(
|
||||
wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
|
||||
{
|
||||
uint32_t newtick = xTaskGetTickCount();
|
||||
|
||||
// one second elapse at least (maybe much more if Wifi was
|
||||
// deconnected for a long)
|
||||
if ((newtick < tickcount) ||
|
||||
((newtick - tickcount) >= configTICK_RATE_HZ)) {
|
||||
tickcount = newtick;
|
||||
dcc_timer_seconds(1);
|
||||
bvlc_maintenance_timer(1);
|
||||
handler_cov_timer_seconds(1);
|
||||
tsm_timer_milliseconds(1000);
|
||||
|
||||
// Read analog values from internal sensors
|
||||
Analog_Input_Present_Value_Set(0, temprature_sens_read());
|
||||
Analog_Input_Present_Value_Set(1, hall_sens_read());
|
||||
}
|
||||
|
||||
pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout);
|
||||
if (pdu_len) {
|
||||
npdu_handler(&src, &Rx_Buf[0], pdu_len);
|
||||
|
||||
if (Binary_Output_Present_Value(0) == BINARY_ACTIVE)
|
||||
gpio_set_level(BACNET_LED, 1);
|
||||
else
|
||||
gpio_set_level(BACNET_LED, 0);
|
||||
}
|
||||
|
||||
handler_cov_task();
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Entry point */
|
||||
void app_main()
|
||||
{
|
||||
// Cannot run BACnet code here, the default stack size is to small : 4096
|
||||
// byte
|
||||
xTaskCreate(BACnetTask, /* Function to implement the task */
|
||||
"BACnetTask", /* Name of the task */
|
||||
10000, /* Stack size in words */
|
||||
NULL, /* Task input parameter */
|
||||
20, /* Priority of the task */
|
||||
NULL); /* Task handle. */
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet MS/TP application entry point for M5StamPLC
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <M5StamPLC.h>
|
||||
|
||||
#ifndef PLC_INPUT_ACTIVE_LOW
|
||||
#define PLC_INPUT_ACTIVE_LOW 0
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include "bacnet_app.h"
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the MS/TP application and PLC hardware
|
||||
*/
|
||||
void setup(void)
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(100);
|
||||
Serial.println("[BOOT] Mode: BACnet MS/TP");
|
||||
Serial.println("[BOOT] Init M5StamPLC...");
|
||||
M5StamPLC.begin();
|
||||
Serial.println("[BOOT] Init BACnet app...");
|
||||
bacnet_app_init();
|
||||
Serial.println("[BOOT] BACnet MS/TP ready");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run the main MS/TP application loop
|
||||
*/
|
||||
void loop(void)
|
||||
{
|
||||
M5StamPLC.update();
|
||||
|
||||
for (uint8_t i = 0; i < bacnet_app_input_count(); i++) {
|
||||
bool state = M5StamPLC.readPlcInput(i);
|
||||
#if PLC_INPUT_ACTIVE_LOW
|
||||
state = !state;
|
||||
#endif
|
||||
bacnet_app_input_set(i, state);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < bacnet_app_relay_count(); i++) {
|
||||
M5StamPLC.writePlcRelay(i, bacnet_app_relay_get(i));
|
||||
}
|
||||
|
||||
bacnet_app_temperature_set(M5StamPLC.getTemp());
|
||||
bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f);
|
||||
|
||||
bacnet_app_tick();
|
||||
delay(1);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet/IP application entry point for ESP32 PlatformIO environments
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <cstring>
|
||||
#if !defined(USE_M5STAMPLC_IO) || (USE_M5STAMPLC_IO)
|
||||
#include <M5StamPLC.h>
|
||||
#endif
|
||||
#include <WiFi.h>
|
||||
#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE)
|
||||
#include <ETH.h>
|
||||
#endif
|
||||
|
||||
#ifndef PLC_INPUT_ACTIVE_LOW
|
||||
#define PLC_INPUT_ACTIVE_LOW 0
|
||||
#endif
|
||||
|
||||
#ifndef USE_M5STAMPLC_IO
|
||||
#define USE_M5STAMPLC_IO 1
|
||||
#endif
|
||||
|
||||
#ifndef USE_ETH_INTERFACE
|
||||
#define USE_ETH_INTERFACE 0
|
||||
#endif
|
||||
|
||||
#ifndef WIFI_SSID
|
||||
#define WIFI_SSID ""
|
||||
#endif
|
||||
|
||||
#ifndef WIFI_PASS
|
||||
#define WIFI_PASS ""
|
||||
#endif
|
||||
|
||||
#ifndef BACNET_IP_PORT
|
||||
#define BACNET_IP_PORT 47808
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include "bacnet_app.h"
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bring up the selected WiFi or Ethernet interface
|
||||
*/
|
||||
static void wifi_connect(void)
|
||||
{
|
||||
#if USE_ETH_INTERFACE
|
||||
Serial.println("[ETH] Starting Ethernet...");
|
||||
if (!ETH.begin()) {
|
||||
Serial.println("[ETH] ETH.begin() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long start = millis();
|
||||
while (!ETH.linkUp() && ((millis() - start) < 15000UL)) {
|
||||
delay(200);
|
||||
Serial.print('.');
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
if (ETH.linkUp()) {
|
||||
Serial.printf(
|
||||
"[ETH] Link up. IP: %s\n", ETH.localIP().toString().c_str());
|
||||
} else {
|
||||
Serial.println("[ETH] Link timeout");
|
||||
}
|
||||
#else
|
||||
if (strlen(WIFI_SSID) == 0) {
|
||||
Serial.println("[WIFI] SSID not configured; skipping WiFi connect");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("[WIFI] Connecting to SSID: %s\n", WIFI_SSID);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
|
||||
unsigned long start = millis();
|
||||
while ((WiFi.status() != WL_CONNECTED) && ((millis() - start) < 15000UL)) {
|
||||
delay(200);
|
||||
Serial.print('.');
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.printf(
|
||||
"[WIFI] Connected. IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
} else {
|
||||
Serial.printf(
|
||||
"[WIFI] Connect timeout. status=%d\n", (int)WiFi.status());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the BACnet/IP application and selected board profile
|
||||
*/
|
||||
void setup(void)
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(100);
|
||||
Serial.println("[BOOT] Mode: BACnet/IP");
|
||||
Serial.printf("[BOOT] BACnet UDP Port: %u\n", (unsigned)BACNET_IP_PORT);
|
||||
#if USE_ETH_INTERFACE
|
||||
Serial.println("[BOOT] Network: Ethernet");
|
||||
#else
|
||||
Serial.println("[BOOT] Network: WiFi");
|
||||
#endif
|
||||
#if USE_M5STAMPLC_IO
|
||||
Serial.println("[BOOT] Board profile: M5StamPLC I/O enabled");
|
||||
M5StamPLC.begin();
|
||||
#else
|
||||
Serial.println("[BOOT] Board profile: Generic/ESP32-PoE (M5 I/O disabled)");
|
||||
#endif
|
||||
wifi_connect();
|
||||
Serial.println("[BOOT] Init BACnet app...");
|
||||
bacnet_app_init();
|
||||
Serial.println("[BOOT] BACnet/IP ready");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run the main BACnet/IP application loop
|
||||
*/
|
||||
void loop(void)
|
||||
{
|
||||
#if USE_M5STAMPLC_IO
|
||||
M5StamPLC.update();
|
||||
|
||||
for (uint8_t i = 0; i < bacnet_app_input_count(); i++) {
|
||||
bool state = M5StamPLC.readPlcInput(i);
|
||||
#if PLC_INPUT_ACTIVE_LOW
|
||||
state = !state;
|
||||
#endif
|
||||
bacnet_app_input_set(i, state);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < bacnet_app_relay_count(); i++) {
|
||||
M5StamPLC.writePlcRelay(i, bacnet_app_relay_get(i));
|
||||
}
|
||||
|
||||
bacnet_app_temperature_set(M5StamPLC.getTemp());
|
||||
bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f);
|
||||
#else
|
||||
bacnet_app_temperature_set(0.0f);
|
||||
bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f);
|
||||
#endif
|
||||
|
||||
bacnet_app_tick();
|
||||
delay(1);
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet router entry point bridging BACnet/IP and BACnet MS/TP
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <cstring>
|
||||
#include <M5StamPLC.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
extern "C" {
|
||||
#include "bacnet_app.h"
|
||||
#include "bip.h"
|
||||
#include "dlenv.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/npdu.h"
|
||||
#include "bacnet/apdu.h"
|
||||
#include "bacnet/datalink/dlmstp.h"
|
||||
#include "bacnet/basic/npdu/h_npdu.h"
|
||||
#include "bacnet/basic/tsm/tsm.h"
|
||||
}
|
||||
|
||||
#ifndef WIFI_SSID
|
||||
#define WIFI_SSID ""
|
||||
#endif
|
||||
|
||||
#ifndef WIFI_PASS
|
||||
#define WIFI_PASS ""
|
||||
#endif
|
||||
|
||||
#ifndef BACNET_IP_PORT
|
||||
#define BACNET_IP_PORT 47808
|
||||
#endif
|
||||
|
||||
#ifndef ROUTER_BIP_NET
|
||||
#define ROUTER_BIP_NET 1
|
||||
#endif
|
||||
|
||||
#ifndef ROUTER_MSTP_NET
|
||||
#define ROUTER_MSTP_NET 200
|
||||
#endif
|
||||
|
||||
#ifndef ROUTER_MSTP_MAC
|
||||
#define ROUTER_MSTP_MAC 2
|
||||
#endif
|
||||
|
||||
#ifndef PLC_INPUT_ACTIVE_LOW
|
||||
#define PLC_INPUT_ACTIVE_LOW 0
|
||||
#endif
|
||||
|
||||
#ifndef GATEWAY_MAX
|
||||
#define GATEWAY_MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
static uint8_t BipRxBuffer[BIP_MPDU_MAX];
|
||||
static uint8_t MstpRxBuffer[DLMSTP_MPDU_MAX];
|
||||
static uint8_t TxBuffer[GATEWAY_MAX(BIP_MPDU_MAX, DLMSTP_MPDU_MAX)];
|
||||
|
||||
/**
|
||||
* @brief Connect the BACnet/IP side of the router to WiFi
|
||||
*/
|
||||
static void wifi_connect(void)
|
||||
{
|
||||
if (strlen(WIFI_SSID) == 0) {
|
||||
Serial.println("[WIFI] SSID not configured; skipping WiFi connect");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("[WIFI] Connecting to SSID: %s\n", WIFI_SSID);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
|
||||
unsigned long start = millis();
|
||||
while ((WiFi.status() != WL_CONNECTED) && ((millis() - start) < 15000UL)) {
|
||||
delay(200);
|
||||
Serial.print('.');
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.printf(
|
||||
"[WIFI] Connected. IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
} else {
|
||||
Serial.printf(
|
||||
"[WIFI] Connect timeout. status=%d\n", (int)WiFi.status());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a BACnet router network message on the MS/TP segment
|
||||
* @param type network layer message type
|
||||
* @param dnet destination network number, or negative when omitted
|
||||
* @return number of bytes sent, or a negative value on error
|
||||
*/
|
||||
static int
|
||||
send_router_message_on_mstp(BACNET_NETWORK_MESSAGE_TYPE type, int dnet)
|
||||
{
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
int pdu_len = 0;
|
||||
|
||||
dest.mac_len = 0;
|
||||
dest.net = BACNET_BROADCAST_NETWORK;
|
||||
dest.len = 0;
|
||||
|
||||
npdu_encode_npdu_network(&npdu_data, type, false, MESSAGE_PRIORITY_NORMAL);
|
||||
pdu_len = npdu_encode_pdu(TxBuffer, &dest, NULL, &npdu_data);
|
||||
if (dnet >= 0) {
|
||||
pdu_len += encode_unsigned16(&TxBuffer[pdu_len], (uint16_t)dnet);
|
||||
}
|
||||
|
||||
return dlmstp_send_pdu(&dest, &npdu_data, TxBuffer, (unsigned)pdu_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a BACnet router network message on the BACnet/IP segment
|
||||
* @param type network layer message type
|
||||
* @param dnet destination network number, or negative when omitted
|
||||
* @return number of bytes sent, or a negative value on error
|
||||
*/
|
||||
static int
|
||||
send_router_message_on_bip(BACNET_NETWORK_MESSAGE_TYPE type, int dnet)
|
||||
{
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
int pdu_len = 0;
|
||||
|
||||
bip_get_broadcast_address(&dest);
|
||||
|
||||
npdu_encode_npdu_network(&npdu_data, type, false, MESSAGE_PRIORITY_NORMAL);
|
||||
pdu_len = npdu_encode_pdu(TxBuffer, &dest, NULL, &npdu_data);
|
||||
if (dnet >= 0) {
|
||||
pdu_len += encode_unsigned16(&TxBuffer[pdu_len], (uint16_t)dnet);
|
||||
}
|
||||
|
||||
return bip_send_pdu(&dest, &npdu_data, TxBuffer, (unsigned)pdu_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether a received NPDU should be processed by the local device
|
||||
* @param dest decoded destination address
|
||||
* @return true if the packet targets the local device or broadcast
|
||||
*/
|
||||
static bool should_process_locally(const BACNET_ADDRESS *dest)
|
||||
{
|
||||
return (dest->net == 0U) || (dest->net == BACNET_BROADCAST_NETWORK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Route an NPDU between BACnet/IP and MS/TP segments
|
||||
* @param from_bip true when the frame arrived from BACnet/IP
|
||||
* @param src decoded source address
|
||||
* @param pdu NPDU buffer to route
|
||||
* @param pdu_len NPDU length in bytes
|
||||
*/
|
||||
static void
|
||||
forward_pdu(bool from_bip, BACNET_ADDRESS *src, uint8_t *pdu, uint16_t pdu_len)
|
||||
{
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
BACNET_ADDRESS routed_src = { 0 };
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
int apdu_offset = bacnet_npdu_decode(pdu, pdu_len, &dest, src, &npdu_data);
|
||||
if (apdu_offset <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t src_net = from_bip ? ROUTER_BIP_NET : ROUTER_MSTP_NET;
|
||||
const uint16_t dst_net = from_bip ? ROUTER_MSTP_NET : ROUTER_BIP_NET;
|
||||
|
||||
if (npdu_data.network_layer_message) {
|
||||
if (npdu_data.network_message_type ==
|
||||
NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK) {
|
||||
uint16_t requested = 0;
|
||||
bool has_dnet = (pdu_len - apdu_offset) >= 2;
|
||||
if (has_dnet) {
|
||||
(void)decode_unsigned16(&pdu[apdu_offset], &requested);
|
||||
}
|
||||
if (!has_dnet || (requested == dst_net)) {
|
||||
if (from_bip) {
|
||||
send_router_message_on_bip(
|
||||
NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, dst_net);
|
||||
} else {
|
||||
send_router_message_on_mstp(
|
||||
NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, dst_net);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((dest.net != 0U) && (dest.net != BACNET_BROADCAST_NETWORK) &&
|
||||
(dest.net != dst_net)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (npdu_data.hop_count > 0) {
|
||||
npdu_data.hop_count--;
|
||||
if (npdu_data.hop_count == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (src->net != 0U) {
|
||||
routed_src = *src;
|
||||
} else {
|
||||
memset(&routed_src, 0, sizeof(routed_src));
|
||||
routed_src.net = src_net;
|
||||
routed_src.len = src->mac_len;
|
||||
for (uint8_t i = 0; i < src->mac_len && i < MAX_MAC_LEN; i++) {
|
||||
routed_src.adr[i] = src->mac[i];
|
||||
}
|
||||
}
|
||||
|
||||
BACNET_ADDRESS out_dest = { 0 };
|
||||
if (dest.net == BACNET_BROADCAST_NETWORK ||
|
||||
((dest.net == dst_net) && (dest.len == 0))) {
|
||||
out_dest.mac_len = 0;
|
||||
out_dest.net = BACNET_BROADCAST_NETWORK;
|
||||
out_dest.len = 0;
|
||||
} else if ((dest.net == dst_net) && (dest.len > 0)) {
|
||||
out_dest.mac_len = dest.len;
|
||||
for (uint8_t i = 0; i < dest.len && i < MAX_MAC_LEN; i++) {
|
||||
out_dest.mac[i] = dest.adr[i];
|
||||
}
|
||||
out_dest.net = 0;
|
||||
out_dest.len = 0;
|
||||
} else {
|
||||
out_dest = dest;
|
||||
}
|
||||
|
||||
int out_len = npdu_encode_pdu(TxBuffer, &out_dest, &routed_src, &npdu_data);
|
||||
if (out_len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t apdu_len = (uint16_t)(pdu_len - apdu_offset);
|
||||
if ((out_len + (int)apdu_len) > (int)sizeof(TxBuffer)) {
|
||||
return;
|
||||
}
|
||||
memcpy(&TxBuffer[out_len], &pdu[apdu_offset], apdu_len);
|
||||
out_len += apdu_len;
|
||||
|
||||
if (from_bip) {
|
||||
(void)dlmstp_send_pdu(
|
||||
&out_dest, &npdu_data, TxBuffer, (unsigned)out_len);
|
||||
} else {
|
||||
(void)bip_send_pdu(&out_dest, &npdu_data, TxBuffer, (unsigned)out_len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the BACnet router and local device services
|
||||
*/
|
||||
void setup(void)
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(100);
|
||||
Serial.println("[BOOT] Mode: BACnet Gateway (Router + Local Device)");
|
||||
Serial.printf(
|
||||
"[GATEWAY] BIP NET=%u MSTP NET=%u MSTP MAC=%u\n",
|
||||
(unsigned)ROUTER_BIP_NET, (unsigned)ROUTER_MSTP_NET,
|
||||
(unsigned)ROUTER_MSTP_MAC);
|
||||
|
||||
M5StamPLC.begin();
|
||||
wifi_connect();
|
||||
|
||||
/* Local device + objects run on B/IP side (same logic as existing M5 app).
|
||||
*/
|
||||
bacnet_app_init();
|
||||
|
||||
if (!m5_dlenv_init((uint8_t)ROUTER_MSTP_MAC)) {
|
||||
Serial.println("[GATEWAY] ERROR: MSTP init failed");
|
||||
for (;;) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
(void)send_router_message_on_bip(
|
||||
NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, ROUTER_MSTP_NET);
|
||||
(void)send_router_message_on_mstp(
|
||||
NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, ROUTER_BIP_NET);
|
||||
|
||||
Serial.println("[GATEWAY] Ready");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run the BACnet router main loop
|
||||
*/
|
||||
void loop(void)
|
||||
{
|
||||
BACNET_ADDRESS src = { 0 };
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
uint16_t pdu_len = 0;
|
||||
|
||||
M5StamPLC.update();
|
||||
|
||||
for (uint8_t i = 0; i < bacnet_app_input_count(); i++) {
|
||||
bool state = M5StamPLC.readPlcInput(i);
|
||||
#if PLC_INPUT_ACTIVE_LOW
|
||||
state = !state;
|
||||
#endif
|
||||
bacnet_app_input_set(i, state);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < bacnet_app_relay_count(); i++) {
|
||||
M5StamPLC.writePlcRelay(i, bacnet_app_relay_get(i));
|
||||
}
|
||||
|
||||
bacnet_app_temperature_set(M5StamPLC.getTemp());
|
||||
bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f);
|
||||
|
||||
pdu_len = bip_receive(&src, BipRxBuffer, sizeof(BipRxBuffer), 0);
|
||||
if (pdu_len > 0) {
|
||||
int apdu_offset =
|
||||
bacnet_npdu_decode(BipRxBuffer, pdu_len, &dest, &src, &npdu_data);
|
||||
if ((apdu_offset > 0) && should_process_locally(&dest)) {
|
||||
npdu_handler(&src, BipRxBuffer, pdu_len);
|
||||
}
|
||||
forward_pdu(true, &src, BipRxBuffer, pdu_len);
|
||||
}
|
||||
|
||||
pdu_len = dlmstp_receive(&src, MstpRxBuffer, sizeof(MstpRxBuffer), 0);
|
||||
if (pdu_len > 0) {
|
||||
forward_pdu(false, &src, MstpRxBuffer, pdu_len);
|
||||
}
|
||||
|
||||
tsm_timer_milliseconds(1);
|
||||
|
||||
delay(1);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet millisecond timer hooks for the PlatformIO ESP32 port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include "mstimer_init.h"
|
||||
|
||||
#include "esp_timer.h"
|
||||
|
||||
/**
|
||||
* @brief Initialize the system timer services used by BACnet
|
||||
*/
|
||||
void systimer_init(void)
|
||||
{
|
||||
/* esp_timer is available after runtime startup on ESP32. */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the current millisecond tick count
|
||||
* @return milliseconds since startup
|
||||
*/
|
||||
unsigned long mstimer_now(void)
|
||||
{
|
||||
return (unsigned long)(esp_timer_get_time() / 1000ULL);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet millisecond timer hook declarations for the PlatformIO ESP32
|
||||
* port
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#ifndef M5STAMPLC_MSTIMER_INIT_H
|
||||
#define M5STAMPLC_MSTIMER_INIT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the system timer services used by BACnet
|
||||
*/
|
||||
void systimer_init(void);
|
||||
|
||||
/**
|
||||
* @brief Get the current millisecond tick count
|
||||
* @return milliseconds since startup
|
||||
*/
|
||||
unsigned long mstimer_now(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief RS485 UART driver used by BACnet MS/TP on ESP32
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#include "rs485.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/uart.h>
|
||||
|
||||
/* UART_SCLK_DEFAULT was introduced in ESP-IDF v5.0.
|
||||
Fall back to UART_SCLK_APB for older IDF/Arduino-ESP32 toolchains. */
|
||||
#ifndef UART_SCLK_DEFAULT
|
||||
#define UART_SCLK_DEFAULT UART_SCLK_APB
|
||||
#endif
|
||||
|
||||
#include "bacnet/basic/sys/mstimer.h"
|
||||
|
||||
static bool Rs485_RTS_Enabled;
|
||||
static uint32_t Rs485_Baud_Rate = RS485_BAUD_RATE;
|
||||
static volatile uint32_t Rs485_Bytes_Tx;
|
||||
static volatile uint32_t Rs485_Bytes_Rx;
|
||||
static struct mstimer Silence_Timer;
|
||||
|
||||
/**
|
||||
* @brief Initialize the RS485 UART peripheral for BACnet MS/TP
|
||||
*/
|
||||
void rs485_init(void)
|
||||
{
|
||||
uart_config_t cfg = { .baud_rate = (int)Rs485_Baud_Rate,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT };
|
||||
|
||||
(void)uart_driver_delete(RS485_UART_NUM);
|
||||
(void)uart_param_config(RS485_UART_NUM, &cfg);
|
||||
(void)uart_set_pin(
|
||||
RS485_UART_NUM, RS485_TX_PIN, RS485_RX_PIN, RS485_DIR_PIN,
|
||||
UART_PIN_NO_CHANGE);
|
||||
(void)uart_driver_install(RS485_UART_NUM, 1024, 0, 0, NULL, 0);
|
||||
(void)uart_set_mode(RS485_UART_NUM, UART_MODE_RS485_HALF_DUPLEX);
|
||||
|
||||
Rs485_RTS_Enabled = false;
|
||||
Rs485_Bytes_Tx = 0;
|
||||
Rs485_Bytes_Rx = 0;
|
||||
rs485_silence_reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the cached RS485 transmit-enable state
|
||||
* @param enable true when transmit is active
|
||||
*/
|
||||
void rs485_rts_enable(bool enable)
|
||||
{
|
||||
/* Hardware RS485 half-duplex mode drives DIR. Keep state for stack queries.
|
||||
*/
|
||||
Rs485_RTS_Enabled = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the RS485 transmit-enable state is active
|
||||
* @return true if transmit is active
|
||||
*/
|
||||
bool rs485_rts_enabled(void)
|
||||
{
|
||||
return Rs485_RTS_Enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one byte from the RS485 receive FIFO when available
|
||||
* @param data_register destination for the received byte, or NULL to probe only
|
||||
* @return true if at least one byte is available
|
||||
*/
|
||||
bool rs485_byte_available(uint8_t *data_register)
|
||||
{
|
||||
size_t buffered = 0;
|
||||
(void)uart_get_buffered_data_len(RS485_UART_NUM, &buffered);
|
||||
if (buffered == 0U) {
|
||||
return false;
|
||||
}
|
||||
if (!data_register) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uart_read_bytes(RS485_UART_NUM, data_register, 1, 0) == 1) {
|
||||
Rs485_Bytes_Rx++;
|
||||
rs485_silence_reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Report whether an RS485 receive error is pending
|
||||
* @return true if a receive error is detected
|
||||
*/
|
||||
bool rs485_receive_error(void)
|
||||
{
|
||||
/* In Arduino-ESP32 builds, low-level UART error queues are not always
|
||||
* exposed. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a block of RS485 bytes
|
||||
* @param buffer transmit buffer
|
||||
* @param nbytes number of bytes to send
|
||||
*/
|
||||
void rs485_bytes_send(const uint8_t *buffer, uint16_t nbytes)
|
||||
{
|
||||
if (!buffer || (nbytes == 0U)) {
|
||||
return;
|
||||
}
|
||||
|
||||
rs485_rts_enable(true);
|
||||
(void)uart_write_bytes(RS485_UART_NUM, (const char *)buffer, nbytes);
|
||||
(void)uart_wait_tx_done(RS485_UART_NUM, pdMS_TO_TICKS(20));
|
||||
Rs485_Bytes_Tx += nbytes;
|
||||
rs485_rts_enable(false);
|
||||
rs485_silence_reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the configured RS485 baud rate
|
||||
* @return baud rate in bits per second
|
||||
*/
|
||||
uint32_t rs485_baud_rate(void)
|
||||
{
|
||||
return Rs485_Baud_Rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the RS485 baud rate
|
||||
* @param baud baud rate in bits per second
|
||||
* @return true if the baud rate was applied
|
||||
*/
|
||||
bool rs485_baud_rate_set(uint32_t baud)
|
||||
{
|
||||
if (baud == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rs485_Baud_Rate = baud;
|
||||
return (uart_set_baudrate(RS485_UART_NUM, (int)Rs485_Baud_Rate) == ESP_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the elapsed silent time on the RS485 bus
|
||||
* @return milliseconds since the last bus activity
|
||||
*/
|
||||
uint32_t rs485_silence_milliseconds(void)
|
||||
{
|
||||
return mstimer_elapsed(&Silence_Timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the RS485 silence timer
|
||||
*/
|
||||
void rs485_silence_reset(void)
|
||||
{
|
||||
mstimer_set(&Silence_Timer, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of transmitted RS485 bytes
|
||||
* @return transmitted byte count
|
||||
*/
|
||||
uint32_t rs485_bytes_transmitted(void)
|
||||
{
|
||||
return Rs485_Bytes_Tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of received RS485 bytes
|
||||
* @return received byte count
|
||||
*/
|
||||
uint32_t rs485_bytes_received(void)
|
||||
{
|
||||
return Rs485_Bytes_Rx;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief RS485 UART driver declarations used by BACnet MS/TP on ESP32
|
||||
* @author Kato Gangstad
|
||||
*/
|
||||
|
||||
#ifndef M5STAMPLC_RS485_H
|
||||
#define M5STAMPLC_RS485_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RS485_UART_NUM UART_NUM_1
|
||||
#define RS485_BAUD_RATE 38400
|
||||
#define RS485_TX_PIN 0
|
||||
#define RS485_RX_PIN 39
|
||||
#define RS485_DIR_PIN 46
|
||||
|
||||
/**
|
||||
* @brief Initialize the RS485 UART peripheral
|
||||
*/
|
||||
void rs485_init(void);
|
||||
|
||||
/**
|
||||
* @brief Update the cached RS485 transmit-enable state
|
||||
* @param enable true when transmit is active
|
||||
*/
|
||||
void rs485_rts_enable(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Check whether the RS485 transmit-enable state is active
|
||||
* @return true if transmit is active
|
||||
*/
|
||||
bool rs485_rts_enabled(void);
|
||||
|
||||
/**
|
||||
* @brief Read one byte from the RS485 receive FIFO when available
|
||||
* @param data_register destination for the received byte, or NULL to probe only
|
||||
* @return true if a byte is available
|
||||
*/
|
||||
bool rs485_byte_available(uint8_t *data_register);
|
||||
|
||||
/**
|
||||
* @brief Report whether an RS485 receive error is pending
|
||||
* @return true if a receive error is detected
|
||||
*/
|
||||
bool rs485_receive_error(void);
|
||||
|
||||
/**
|
||||
* @brief Send a block of RS485 bytes
|
||||
* @param buffer transmit buffer
|
||||
* @param nbytes number of bytes to send
|
||||
*/
|
||||
void rs485_bytes_send(const uint8_t *buffer, uint16_t nbytes);
|
||||
|
||||
/**
|
||||
* @brief Get the configured RS485 baud rate
|
||||
* @return baud rate in bits per second
|
||||
*/
|
||||
uint32_t rs485_baud_rate(void);
|
||||
|
||||
/**
|
||||
* @brief Set the RS485 baud rate
|
||||
* @param baud baud rate in bits per second
|
||||
* @return true if the baud rate was applied
|
||||
*/
|
||||
bool rs485_baud_rate_set(uint32_t baud);
|
||||
|
||||
/**
|
||||
* @brief Get the elapsed silent time on the RS485 bus
|
||||
* @return milliseconds since the last bus activity
|
||||
*/
|
||||
uint32_t rs485_silence_milliseconds(void);
|
||||
|
||||
/**
|
||||
* @brief Reset the RS485 silence timer
|
||||
*/
|
||||
void rs485_silence_reset(void);
|
||||
|
||||
/**
|
||||
* @brief Get the number of transmitted RS485 bytes
|
||||
* @return transmitted byte count
|
||||
*/
|
||||
uint32_t rs485_bytes_transmitted(void);
|
||||
|
||||
/**
|
||||
* @brief Get the number of received RS485 bytes
|
||||
* @return received byte count
|
||||
*/
|
||||
uint32_t rs485_bytes_received(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Automatically generated file; DO NOT EDIT.
|
||||
* Espressif IoT Development Framework Configuration
|
||||
*
|
||||
*/
|
||||
#define CONFIG_GATTC_ENABLE 1
|
||||
#define CONFIG_ESP32_PHY_MAX_TX_POWER 20
|
||||
#define CONFIG_PHY_ENABLED 1
|
||||
#define CONFIG_TRACEMEM_RESERVE_DRAM 0x0
|
||||
#define CONFIG_FREERTOS_MAX_TASK_NAME_LEN 16
|
||||
#define CONFIG_BLE_SMP_ENABLE 1
|
||||
#define CONFIG_IPC_TASK_STACK_SIZE 1024
|
||||
#define CONFIG_ESPTOOLPY_FLASHFREQ "40m"
|
||||
#define CONFIG_NEWLIB_STDOUT_ADDCR 1
|
||||
#define CONFIG_TASK_WDT_CHECK_IDLE_TASK 1
|
||||
#define CONFIG_ESPTOOLPY_FLASHSIZE "2MB"
|
||||
#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER 1
|
||||
#define CONFIG_ETHERNET 1
|
||||
#define CONFIG_INT_WDT 1
|
||||
#define CONFIG_ESPTOOLPY_FLASHFREQ_40M 1
|
||||
#define CONFIG_LOG_BOOTLOADER_LEVEL_INFO 1
|
||||
#define CONFIG_ESPTOOLPY_FLASHSIZE_2MB 1
|
||||
#define CONFIG_AWS_IOT_MQTT_PORT 8883
|
||||
#define CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS 1
|
||||
#define CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM 10
|
||||
#define CONFIG_LOG_DEFAULT_LEVEL_INFO 1
|
||||
#define CONFIG_BT_RESERVE_DRAM 0x10000
|
||||
#define CONFIG_ESP32_PANIC_PRINT_REBOOT 1
|
||||
#define CONFIG_CONSOLE_UART_BAUDRATE 115200
|
||||
#define CONFIG_LWIP_MAX_SOCKETS 10
|
||||
#define CONFIG_EMAC_TASK_PRIORITY 20
|
||||
#define CONFIG_TIMER_TASK_STACK_DEPTH 2048
|
||||
#define CONFIG_FATFS_CODEPAGE 1
|
||||
#define CONFIG_ESP32_DEFAULT_CPU_FREQ_160 1
|
||||
#define CONFIG_ULP_COPROC_RESERVE_MEM 0
|
||||
#define CONFIG_ESPTOOLPY_BAUD 115200
|
||||
#define CONFIG_INT_WDT_CHECK_CPU1 1
|
||||
#define CONFIG_FLASHMODE_DIO 1
|
||||
#define CONFIG_ESPTOOLPY_AFTER_RESET 1
|
||||
#define CONFIG_TOOLPREFIX "xtensa-esp32-elf-"
|
||||
#define CONFIG_FREERTOS_IDLE_TASK_STACKSIZE 1024
|
||||
#define CONFIG_ESP32_WIFI_AMPDU_ENABLED 1
|
||||
#define CONFIG_CONSOLE_UART_NUM 0
|
||||
#define CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC 1
|
||||
#define CONFIG_ESPTOOLPY_BAUD_115200B 1
|
||||
#define CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX 0
|
||||
#define CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS 1
|
||||
#define CONFIG_CONSOLE_UART_DEFAULT 1
|
||||
#define CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN 16384
|
||||
#define CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS 4
|
||||
#define CONFIG_ESPTOOLPY_FLASHSIZE_DETECT 1
|
||||
#define CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE 1
|
||||
#define CONFIG_BTDM_CONTROLLER_RUN_CPU 0
|
||||
#define CONFIG_TCPIP_TASK_STACK_SIZE 2560
|
||||
#define CONFIG_TASK_WDT 1
|
||||
#define CONFIG_MAIN_TASK_STACK_SIZE 4096
|
||||
#define CONFIG_TASK_WDT_TIMEOUT_S 5
|
||||
#define CONFIG_INT_WDT_TIMEOUT_MS 300
|
||||
#define CONFIG_ESPTOOLPY_FLASHMODE "dio"
|
||||
#define CONFIG_BTC_TASK_STACK_SIZE 3072
|
||||
#define CONFIG_BLUEDROID_ENABLED 1
|
||||
#define CONFIG_ESPTOOLPY_BEFORE "default_reset"
|
||||
#define CONFIG_LOG_DEFAULT_LEVEL 3
|
||||
#define CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION 1
|
||||
#define CONFIG_TIMER_QUEUE_LENGTH 10
|
||||
#define CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM 32
|
||||
#define CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER 20
|
||||
#define CONFIG_ESP32_RTC_CLK_CAL_CYCLES 1024
|
||||
#define CONFIG_ESP32_WIFI_NVS_ENABLED 1
|
||||
#define CONFIG_AWS_IOT_SDK 1
|
||||
#define CONFIG_DMA_RX_BUF_NUM 10
|
||||
#define CONFIG_TCP_SYNMAXRTX 6
|
||||
#define CONFIG_PYTHON "python"
|
||||
#define CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1 1
|
||||
#define CONFIG_ESPTOOLPY_COMPRESSED 1
|
||||
#define CONFIG_PARTITION_TABLE_FILENAME "partitions_singleapp.csv"
|
||||
#define CONFIG_LWIP_DHCP_MAX_NTP_SERVERS 1
|
||||
#define CONFIG_PARTITION_TABLE_SINGLE_APP 1
|
||||
#define CONFIG_WIFI_ENABLED 1
|
||||
#define CONFIG_LWIP_DHCP_DOES_ARP_CHECK 1
|
||||
#define CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE 4096
|
||||
#define CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY 2000
|
||||
#define CONFIG_ESP32_APPTRACE_DEST_NONE 1
|
||||
#define CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET 0x10000
|
||||
#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM 32
|
||||
#define CONFIG_FATFS_CODEPAGE_ASCII 1
|
||||
#define CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 1
|
||||
#define CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ 160
|
||||
#define CONFIG_FREERTOS_HZ 100
|
||||
#define CONFIG_LOG_COLORS 1
|
||||
#define CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE 1
|
||||
#define CONFIG_FREERTOS_ASSERT_FAIL_ABORT 1
|
||||
#define CONFIG_ESP32_XTAL_FREQ 0
|
||||
#define CONFIG_MONITOR_BAUD_115200B 1
|
||||
#define CONFIG_LOG_BOOTLOADER_LEVEL 3
|
||||
#define CONFIG_SMP_ENABLE 1
|
||||
#define CONFIG_ESPTOOLPY_BEFORE_RESET 1
|
||||
#define CONFIG_ESPTOOLPY_BAUD_OTHER_VAL 115200
|
||||
#define CONFIG_ESP32_XTAL_FREQ_AUTO 1
|
||||
#define CONFIG_TCP_MAXRTX 12
|
||||
#define CONFIG_ESPTOOLPY_AFTER "hard_reset"
|
||||
#define CONFIG_DMA_TX_BUF_NUM 10
|
||||
#define CONFIG_ESP32_DEBUG_OCDAWARE 1
|
||||
#define CONFIG_TIMER_TASK_PRIORITY 1
|
||||
#define CONFIG_BT_ENABLED 1
|
||||
#define CONFIG_MONITOR_BAUD 115200
|
||||
#define CONFIG_FREERTOS_CORETIMER_0 1
|
||||
#define CONFIG_PARTITION_TABLE_CUSTOM_FILENAME "partitions.csv"
|
||||
#define CONFIG_MBEDTLS_HAVE_TIME 1
|
||||
#define CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY 1
|
||||
#define CONFIG_GATTS_ENABLE 1
|
||||
#define CONFIG_FREERTOS_ISR_STACKSIZE 1536
|
||||
#define CONFIG_OPENSSL_ASSERT_DO_NOTHING 1
|
||||
#define CONFIG_AWS_IOT_MQTT_HOST ""
|
||||
#define CONFIG_SYSTEM_EVENT_QUEUE_SIZE 32
|
||||
#define CONFIG_BT_ACL_CONNECTIONS 4
|
||||
#define CONFIG_FATFS_MAX_LFN 255
|
||||
#define CONFIG_ESP32_WIFI_TX_BUFFER_TYPE 1
|
||||
#define CONFIG_APP_OFFSET 0x10000
|
||||
#define CONFIG_MEMMAP_SMP 1
|
||||
#define CONFIG_SPI_FLASH_ROM_DRIVER_PATCH 1
|
||||
#define CONFIG_MONITOR_BAUD_OTHER_VAL 115200
|
||||
#define CONFIG_ESPTOOLPY_PORT "/dev/ttyUSB0"
|
||||
#define CONFIG_OPTIMIZATION_LEVEL_RELEASE 1
|
||||
Reference in New Issue
Block a user