esp32: replace port with PlatformIO implementation and add CI build (#1292)

This commit is contained in:
Kato Gangstad
2026-04-06 15:33:17 +02:00
committed by GitHub
parent 3d668f2f96
commit 589a61b287
38 changed files with 2926 additions and 4708 deletions
+12
View File
@@ -328,3 +328,15 @@ jobs:
gcc --version gcc --version
make clean make clean
make gtk-discover 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
+5
View File
@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
+132
View File
@@ -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
![XIAO ESP32C3](docs/board-photos/Xiao.webp)
### 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
![M5StamPLC](docs/board-photos/M5-StamPlc.webp)
### 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
![Olimex ESP32-POE](docs/board-photos/Olimex-ESP32-POE.jpg)
+21
View File
@@ -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

+120
View File
@@ -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}>",
)
-36
View File
@@ -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
View File
@@ -6,14 +6,155 @@
; Advanced options: extra scripting ; Advanced options: extra scripting
; ;
; Please visit documentation for the other options and examples ; 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 platform = espressif32
board = esp32thing framework = arduino
framework = espidf 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 [env:m5stamplc-mstp]
upload_speed = 1152000 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
-67
View File
@@ -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
-7
View File
@@ -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
-1294
View File
File diff suppressed because it is too large Load Diff
-151
View File
@@ -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
+365
View File
@@ -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);
}
+69
View File
@@ -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
+350
View File
@@ -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);
}
+226
View File
@@ -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
View File
@@ -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 <stdbool.h>
#include "esp_wifi.h" #include <stdint.h>
#include "lwip/sockets.h" #include "bip.h"
#include "lwip/netdb.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; 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()); (void)BIP_Debug;
bip_set_socket(-1);
}
bool bip_init(char *ifname) if (!bip_socket_init(port)) {
{ return false;
tcpip_adapter_ip_info_t ip_info = { 0 }; }
int value = 1; bip_set_interface();
bip_set_port(port);
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); bip_set_socket(0);
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);
return true; return true;
} }
/**
* @brief Shut down the BACnet/IP datalink
*/
void bip_cleanup(void)
{
if (bip_valid()) {
bip_socket_cleanup();
}
}
+178
View File
@@ -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;
}
-433
View File
@@ -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;
}
-118
View File
@@ -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
+164
View File
@@ -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;
}
+46
View File
@@ -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
-461
View File
@@ -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
+81
View File
@@ -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)
{
}
+48
View File
@@ -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
-218
View File
@@ -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. */
}
+57
View File
@@ -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);
}
+152
View File
@@ -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);
}
+332
View File
@@ -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);
}
+26
View File
@@ -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);
}
+32
View File
@@ -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
+184
View File
@@ -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;
}
+100
View File
@@ -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
-125
View File
@@ -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