From 53e123dc8a56bf1fe65918dc5c08fd66315364b1 Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Sat, 17 Jan 2026 12:22:45 +0100 Subject: [PATCH] First Try --- CMakeLists.txt | 12 +- components/knx/CMakeLists.txt | 11 + components/knx/src/arduino_platform.cpp | 315 +++ components/knx/src/arduino_platform.h | 47 + components/knx/src/cc1310_platform.cpp | 587 +++++ components/knx/src/cc1310_platform.h | 27 + components/knx/src/esp32_idf_platform.cpp | 443 ++++ components/knx/src/esp32_idf_platform.h | 89 + components/knx/src/esp32_platform.cpp | 184 ++ components/knx/src/esp32_platform.h | 54 + components/knx/src/esp_platform.cpp | 135 ++ components/knx/src/esp_platform.h | 43 + components/knx/src/knx.h | 248 ++ .../knx/src/knx/address_table_object.cpp | 96 + components/knx/src/knx/address_table_object.h | 57 + components/knx/src/knx/aes.c | 606 +++++ components/knx/src/knx/aes.h | 90 + components/knx/src/knx/aes.hpp | 12 + components/knx/src/knx/apdu.cpp | 59 + components/knx/src/knx/apdu.h | 53 + components/knx/src/knx/application_layer.cpp | 1546 +++++++++++++ components/knx/src/knx/application_layer.h | 228 ++ .../src/knx/application_program_object.cpp | 99 + .../knx/src/knx/application_program_object.h | 18 + .../knx/src/knx/association_table_object.cpp | 174 ++ .../knx/src/knx/association_table_object.h | 25 + components/knx/src/knx/bau.cpp | 392 ++++ components/knx/src/knx/bau.h | 184 ++ components/knx/src/knx/bau07B0.cpp | 178 ++ components/knx/src/knx/bau07B0.h | 34 + components/knx/src/knx/bau091A.cpp | 262 +++ components/knx/src/knx/bau091A.h | 42 + components/knx/src/knx/bau27B0.cpp | 207 ++ components/knx/src/knx/bau27B0.h | 47 + components/knx/src/knx/bau2920.cpp | 181 ++ components/knx/src/knx/bau2920.h | 43 + components/knx/src/knx/bau57B0.cpp | 169 ++ components/knx/src/knx/bau57B0.h | 33 + components/knx/src/knx/bau_systemB.cpp | 812 +++++++ components/knx/src/knx/bau_systemB.h | 133 ++ .../knx/src/knx/bau_systemB_coupler.cpp | 60 + components/knx/src/knx/bau_systemB_coupler.h | 40 + components/knx/src/knx/bau_systemB_device.cpp | 262 +++ components/knx/src/knx/bau_systemB_device.h | 57 + components/knx/src/knx/bits.cpp | 364 +++ components/knx/src/knx/bits.h | 161 ++ components/knx/src/knx/callback_property.h | 39 + components/knx/src/knx/cemi_frame.cpp | 403 ++++ components/knx/src/knx/cemi_frame.h | 94 + components/knx/src/knx/cemi_server.cpp | 443 ++++ components/knx/src/knx/cemi_server.h | 68 + components/knx/src/knx/cemi_server_object.cpp | 61 + components/knx/src/knx/cemi_server_object.h | 17 + components/knx/src/knx/config.h | 84 + components/knx/src/knx/data_link_layer.cpp | 309 +++ components/knx/src/knx/data_link_layer.h | 77 + components/knx/src/knx/data_property.cpp | 168 ++ components/knx/src/knx/data_property.h | 25 + components/knx/src/knx/datapoint_types.cpp | 64 + components/knx/src/knx/datapoint_types.h | 37 + components/knx/src/knx/device_object.cpp | 294 +++ components/knx/src/knx/device_object.h | 51 + components/knx/src/knx/dpt.cpp | 23 + components/knx/src/knx/dpt.h | 373 +++ components/knx/src/knx/dptconvert.cpp | 2008 +++++++++++++++++ components/knx/src/knx/dptconvert.h | 149 ++ components/knx/src/knx/function_property.h | 53 + components/knx/src/knx/group_object.cpp | 356 +++ components/knx/src/knx/group_object.h | 287 +++ .../knx/src/knx/group_object_table_object.cpp | 131 ++ .../knx/src/knx/group_object_table_object.h | 29 + components/knx/src/knx/interface_object.cpp | 232 ++ components/knx/src/knx/interface_object.h | 210 ++ components/knx/src/knx/ip_data_link_layer.cpp | 1191 ++++++++++ components/knx/src/knx/ip_data_link_layer.h | 59 + .../ip_host_protocol_address_information.cpp | 48 + .../ip_host_protocol_address_information.h | 33 + .../knx/src/knx/ip_parameter_object.cpp | 143 ++ components/knx/src/knx/ip_parameter_object.h | 20 + components/knx/src/knx/knx_ip_ch.cpp | 48 + components/knx/src/knx/knx_ip_ch.h | 28 + components/knx/src/knx/knx_ip_config_dib.cpp | 95 + components/knx/src/knx/knx_ip_config_dib.h | 28 + .../knx/src/knx/knx_ip_config_request.cpp | 17 + .../knx/src/knx/knx_ip_config_request.h | 17 + .../knx/src/knx/knx_ip_connect_request.cpp | 21 + .../knx/src/knx/knx_ip_connect_request.h | 19 + .../knx/src/knx/knx_ip_connect_response.cpp | 46 + .../knx/src/knx/knx_ip_connect_response.h | 45 + components/knx/src/knx/knx_ip_crd.cpp | 41 + components/knx/src/knx/knx_ip_crd.h | 23 + components/knx/src/knx/knx_ip_cri.cpp | 38 + components/knx/src/knx/knx_ip_cri.h | 36 + .../src/knx/knx_ip_description_request.cpp | 13 + .../knx/src/knx/knx_ip_description_request.h | 15 + .../src/knx/knx_ip_description_response.cpp | 72 + .../knx/src/knx/knx_ip_description_response.h | 21 + .../src/knx/knx_ip_device_information_dib.cpp | 102 + .../src/knx/knx_ip_device_information_dib.h | 32 + components/knx/src/knx/knx_ip_dib.cpp | 28 + components/knx/src/knx/knx_ip_dib.h | 34 + .../knx/src/knx/knx_ip_disconnect_request.cpp | 26 + .../knx/src/knx/knx_ip_disconnect_request.h | 17 + .../src/knx/knx_ip_disconnect_response.cpp | 12 + .../knx/src/knx/knx_ip_disconnect_response.h | 13 + ...knx_ip_extended_device_information_dib.cpp | 42 + .../knx_ip_extended_device_information_dib.h | 19 + components/knx/src/knx/knx_ip_frame.cpp | 81 + components/knx/src/knx/knx_ip_frame.h | 57 + .../knx/src/knx/knx_ip_knx_addresses_dib.cpp | 27 + .../knx/src/knx/knx_ip_knx_addresses_dib.h | 17 + .../knx/src/knx/knx_ip_routing_indication.cpp | 22 + .../knx/src/knx/knx_ip_routing_indication.h | 16 + .../knx/src/knx/knx_ip_search_request.cpp | 13 + .../knx/src/knx/knx_ip_search_request.h | 14 + .../knx/knx_ip_search_request_extended.cpp | 69 + .../src/knx/knx_ip_search_request_extended.h | 26 + .../knx/src/knx/knx_ip_search_response.cpp | 84 + .../knx/src/knx/knx_ip_search_response.h | 24 + .../knx/knx_ip_search_response_extended.cpp | 227 ++ .../src/knx/knx_ip_search_response_extended.h | 38 + .../knx/src/knx/knx_ip_state_request.cpp | 16 + components/knx/src/knx/knx_ip_state_request.h | 17 + .../knx/src/knx/knx_ip_state_response.cpp | 26 + .../knx/src/knx/knx_ip_state_response.h | 13 + .../src/knx/knx_ip_supported_service_dib.cpp | 44 + .../src/knx/knx_ip_supported_service_dib.h | 23 + .../knx/src/knx/knx_ip_tunnel_connection.cpp | 24 + .../knx/src/knx/knx_ip_tunnel_connection.h | 24 + .../knx/src/knx/knx_ip_tunneling_ack.cpp | 20 + components/knx/src/knx/knx_ip_tunneling_ack.h | 17 + .../knx/src/knx/knx_ip_tunneling_info_dib.cpp | 31 + .../knx/src/knx/knx_ip_tunneling_info_dib.h | 20 + .../knx/src/knx/knx_ip_tunneling_request.cpp | 26 + .../knx/src/knx/knx_ip_tunneling_request.h | 19 + components/knx/src/knx/knx_types.h | 290 +++ components/knx/src/knx/knx_value.cpp | 626 +++++ components/knx/src/knx/knx_value.h | 99 + components/knx/src/knx/memory.cpp | 562 +++++ components/knx/src/knx/memory.h | 87 + components/knx/src/knx/network_layer.cpp | 46 + components/knx/src/knx/network_layer.h | 46 + .../knx/src/knx/network_layer_coupler.cpp | 656 ++++++ .../knx/src/knx/network_layer_coupler.h | 80 + .../knx/src/knx/network_layer_device.cpp | 150 ++ components/knx/src/knx/network_layer_device.h | 44 + .../knx/src/knx/network_layer_entity.cpp | 74 + components/knx/src/knx/network_layer_entity.h | 44 + components/knx/src/knx/npdu.cpp | 44 + components/knx/src/knx/npdu.h | 27 + components/knx/src/knx/platform.cpp | 451 ++++ components/knx/src/knx/platform.h | 152 ++ components/knx/src/knx/property.cpp | 245 ++ components/knx/src/knx/property.h | 292 +++ components/knx/src/knx/rf_data_link_layer.cpp | 385 ++++ components/knx/src/knx/rf_data_link_layer.h | 67 + components/knx/src/knx/rf_medium_object.cpp | 60 + components/knx/src/knx/rf_medium_object.h | 19 + components/knx/src/knx/rf_physical_layer.h | 30 + .../knx/src/knx/rf_physical_layer_cc1101.cpp | 784 +++++++ .../knx/src/knx/rf_physical_layer_cc1101.h | 244 ++ .../knx/src/knx/rf_physical_layer_cc1310.cpp | 377 ++++ .../knx/src/knx/rf_physical_layer_cc1310.h | 41 + components/knx/src/knx/router_object.cpp | 603 +++++ components/knx/src/knx/router_object.h | 58 + components/knx/src/knx/save_restore.h | 43 + .../knx/src/knx/secure_application_layer.cpp | 1336 +++++++++++ .../knx/src/knx/secure_application_layer.h | 162 ++ .../knx/src/knx/security_interface_object.cpp | 605 +++++ .../knx/src/knx/security_interface_object.h | 64 + components/knx/src/knx/service_families.h | 15 + components/knx/src/knx/simple_map.h | 134 ++ components/knx/src/knx/table_object.cpp | 414 ++++ components/knx/src/knx/table_object.h | 94 + components/knx/src/knx/tp_frame.h | 309 +++ components/knx/src/knx/tpdu.cpp | 132 ++ components/knx/src/knx/tpdu.h | 36 + .../knx/src/knx/tpuart_data_link_layer.cpp | 1250 ++++++++++ .../knx/src/knx/tpuart_data_link_layer.h | 180 ++ components/knx/src/knx/transport_layer.cpp | 799 +++++++ components/knx/src/knx/transport_layer.h | 121 + .../knx/src/knx/usb_tunnel_interface.cpp | 565 +++++ components/knx/src/knx/usb_tunnel_interface.h | 96 + components/knx/src/knx_facade.cpp | 133 ++ components/knx/src/knx_facade.h | 589 +++++ components/knx/src/libretiny_platform.cpp | 173 ++ components/knx/src/libretiny_platform.h | 53 + components/knx/src/linux_platform.cpp | 1232 ++++++++++ components/knx/src/linux_platform.h | 81 + .../knx/src/rp2040_arduino_platform.cpp | 578 +++++ components/knx/src/rp2040_arduino_platform.h | 157 ++ components/knx/src/samd_platform.cpp | 242 ++ components/knx/src/samd_platform.h | 57 + components/knx/src/stm32_platform.cpp | 74 + components/knx/src/stm32_platform.h | 25 + dependencies.lock | 227 +- main/CMakeLists.txt | 4 +- main/Display.cpp | 101 + main/Display.hpp | 13 + main/Gui.cpp | 46 + main/Gui.hpp | 12 + main/KnxWorker.cpp | 24 + main/KnxWorker.hpp | 8 + main/Nvs.cpp | 15 + main/Nvs.hpp | 7 + main/Touch.cpp | 81 + main/Touch.hpp | 16 + main/hello_world_main.c | 239 -- main/main.cpp | 76 + 209 files changed, 35370 insertions(+), 412 deletions(-) create mode 100644 components/knx/CMakeLists.txt create mode 100644 components/knx/src/arduino_platform.cpp create mode 100644 components/knx/src/arduino_platform.h create mode 100644 components/knx/src/cc1310_platform.cpp create mode 100644 components/knx/src/cc1310_platform.h create mode 100644 components/knx/src/esp32_idf_platform.cpp create mode 100644 components/knx/src/esp32_idf_platform.h create mode 100644 components/knx/src/esp32_platform.cpp create mode 100644 components/knx/src/esp32_platform.h create mode 100644 components/knx/src/esp_platform.cpp create mode 100644 components/knx/src/esp_platform.h create mode 100644 components/knx/src/knx.h create mode 100644 components/knx/src/knx/address_table_object.cpp create mode 100644 components/knx/src/knx/address_table_object.h create mode 100644 components/knx/src/knx/aes.c create mode 100644 components/knx/src/knx/aes.h create mode 100644 components/knx/src/knx/aes.hpp create mode 100644 components/knx/src/knx/apdu.cpp create mode 100644 components/knx/src/knx/apdu.h create mode 100644 components/knx/src/knx/application_layer.cpp create mode 100644 components/knx/src/knx/application_layer.h create mode 100644 components/knx/src/knx/application_program_object.cpp create mode 100644 components/knx/src/knx/application_program_object.h create mode 100644 components/knx/src/knx/association_table_object.cpp create mode 100644 components/knx/src/knx/association_table_object.h create mode 100644 components/knx/src/knx/bau.cpp create mode 100644 components/knx/src/knx/bau.h create mode 100644 components/knx/src/knx/bau07B0.cpp create mode 100644 components/knx/src/knx/bau07B0.h create mode 100644 components/knx/src/knx/bau091A.cpp create mode 100644 components/knx/src/knx/bau091A.h create mode 100644 components/knx/src/knx/bau27B0.cpp create mode 100644 components/knx/src/knx/bau27B0.h create mode 100644 components/knx/src/knx/bau2920.cpp create mode 100644 components/knx/src/knx/bau2920.h create mode 100644 components/knx/src/knx/bau57B0.cpp create mode 100644 components/knx/src/knx/bau57B0.h create mode 100644 components/knx/src/knx/bau_systemB.cpp create mode 100644 components/knx/src/knx/bau_systemB.h create mode 100644 components/knx/src/knx/bau_systemB_coupler.cpp create mode 100644 components/knx/src/knx/bau_systemB_coupler.h create mode 100644 components/knx/src/knx/bau_systemB_device.cpp create mode 100644 components/knx/src/knx/bau_systemB_device.h create mode 100644 components/knx/src/knx/bits.cpp create mode 100644 components/knx/src/knx/bits.h create mode 100644 components/knx/src/knx/callback_property.h create mode 100644 components/knx/src/knx/cemi_frame.cpp create mode 100644 components/knx/src/knx/cemi_frame.h create mode 100644 components/knx/src/knx/cemi_server.cpp create mode 100644 components/knx/src/knx/cemi_server.h create mode 100644 components/knx/src/knx/cemi_server_object.cpp create mode 100644 components/knx/src/knx/cemi_server_object.h create mode 100644 components/knx/src/knx/config.h create mode 100644 components/knx/src/knx/data_link_layer.cpp create mode 100644 components/knx/src/knx/data_link_layer.h create mode 100644 components/knx/src/knx/data_property.cpp create mode 100644 components/knx/src/knx/data_property.h create mode 100644 components/knx/src/knx/datapoint_types.cpp create mode 100644 components/knx/src/knx/datapoint_types.h create mode 100644 components/knx/src/knx/device_object.cpp create mode 100644 components/knx/src/knx/device_object.h create mode 100644 components/knx/src/knx/dpt.cpp create mode 100644 components/knx/src/knx/dpt.h create mode 100644 components/knx/src/knx/dptconvert.cpp create mode 100644 components/knx/src/knx/dptconvert.h create mode 100644 components/knx/src/knx/function_property.h create mode 100644 components/knx/src/knx/group_object.cpp create mode 100644 components/knx/src/knx/group_object.h create mode 100644 components/knx/src/knx/group_object_table_object.cpp create mode 100644 components/knx/src/knx/group_object_table_object.h create mode 100644 components/knx/src/knx/interface_object.cpp create mode 100644 components/knx/src/knx/interface_object.h create mode 100644 components/knx/src/knx/ip_data_link_layer.cpp create mode 100644 components/knx/src/knx/ip_data_link_layer.h create mode 100644 components/knx/src/knx/ip_host_protocol_address_information.cpp create mode 100644 components/knx/src/knx/ip_host_protocol_address_information.h create mode 100644 components/knx/src/knx/ip_parameter_object.cpp create mode 100644 components/knx/src/knx/ip_parameter_object.h create mode 100644 components/knx/src/knx/knx_ip_ch.cpp create mode 100644 components/knx/src/knx/knx_ip_ch.h create mode 100644 components/knx/src/knx/knx_ip_config_dib.cpp create mode 100644 components/knx/src/knx/knx_ip_config_dib.h create mode 100644 components/knx/src/knx/knx_ip_config_request.cpp create mode 100644 components/knx/src/knx/knx_ip_config_request.h create mode 100644 components/knx/src/knx/knx_ip_connect_request.cpp create mode 100644 components/knx/src/knx/knx_ip_connect_request.h create mode 100644 components/knx/src/knx/knx_ip_connect_response.cpp create mode 100644 components/knx/src/knx/knx_ip_connect_response.h create mode 100644 components/knx/src/knx/knx_ip_crd.cpp create mode 100644 components/knx/src/knx/knx_ip_crd.h create mode 100644 components/knx/src/knx/knx_ip_cri.cpp create mode 100644 components/knx/src/knx/knx_ip_cri.h create mode 100644 components/knx/src/knx/knx_ip_description_request.cpp create mode 100644 components/knx/src/knx/knx_ip_description_request.h create mode 100644 components/knx/src/knx/knx_ip_description_response.cpp create mode 100644 components/knx/src/knx/knx_ip_description_response.h create mode 100644 components/knx/src/knx/knx_ip_device_information_dib.cpp create mode 100644 components/knx/src/knx/knx_ip_device_information_dib.h create mode 100644 components/knx/src/knx/knx_ip_dib.cpp create mode 100644 components/knx/src/knx/knx_ip_dib.h create mode 100644 components/knx/src/knx/knx_ip_disconnect_request.cpp create mode 100644 components/knx/src/knx/knx_ip_disconnect_request.h create mode 100644 components/knx/src/knx/knx_ip_disconnect_response.cpp create mode 100644 components/knx/src/knx/knx_ip_disconnect_response.h create mode 100644 components/knx/src/knx/knx_ip_extended_device_information_dib.cpp create mode 100644 components/knx/src/knx/knx_ip_extended_device_information_dib.h create mode 100644 components/knx/src/knx/knx_ip_frame.cpp create mode 100644 components/knx/src/knx/knx_ip_frame.h create mode 100644 components/knx/src/knx/knx_ip_knx_addresses_dib.cpp create mode 100644 components/knx/src/knx/knx_ip_knx_addresses_dib.h create mode 100644 components/knx/src/knx/knx_ip_routing_indication.cpp create mode 100644 components/knx/src/knx/knx_ip_routing_indication.h create mode 100644 components/knx/src/knx/knx_ip_search_request.cpp create mode 100644 components/knx/src/knx/knx_ip_search_request.h create mode 100644 components/knx/src/knx/knx_ip_search_request_extended.cpp create mode 100644 components/knx/src/knx/knx_ip_search_request_extended.h create mode 100644 components/knx/src/knx/knx_ip_search_response.cpp create mode 100644 components/knx/src/knx/knx_ip_search_response.h create mode 100644 components/knx/src/knx/knx_ip_search_response_extended.cpp create mode 100644 components/knx/src/knx/knx_ip_search_response_extended.h create mode 100644 components/knx/src/knx/knx_ip_state_request.cpp create mode 100644 components/knx/src/knx/knx_ip_state_request.h create mode 100644 components/knx/src/knx/knx_ip_state_response.cpp create mode 100644 components/knx/src/knx/knx_ip_state_response.h create mode 100644 components/knx/src/knx/knx_ip_supported_service_dib.cpp create mode 100644 components/knx/src/knx/knx_ip_supported_service_dib.h create mode 100644 components/knx/src/knx/knx_ip_tunnel_connection.cpp create mode 100644 components/knx/src/knx/knx_ip_tunnel_connection.h create mode 100644 components/knx/src/knx/knx_ip_tunneling_ack.cpp create mode 100644 components/knx/src/knx/knx_ip_tunneling_ack.h create mode 100644 components/knx/src/knx/knx_ip_tunneling_info_dib.cpp create mode 100644 components/knx/src/knx/knx_ip_tunneling_info_dib.h create mode 100644 components/knx/src/knx/knx_ip_tunneling_request.cpp create mode 100644 components/knx/src/knx/knx_ip_tunneling_request.h create mode 100644 components/knx/src/knx/knx_types.h create mode 100644 components/knx/src/knx/knx_value.cpp create mode 100644 components/knx/src/knx/knx_value.h create mode 100644 components/knx/src/knx/memory.cpp create mode 100644 components/knx/src/knx/memory.h create mode 100644 components/knx/src/knx/network_layer.cpp create mode 100644 components/knx/src/knx/network_layer.h create mode 100644 components/knx/src/knx/network_layer_coupler.cpp create mode 100644 components/knx/src/knx/network_layer_coupler.h create mode 100644 components/knx/src/knx/network_layer_device.cpp create mode 100644 components/knx/src/knx/network_layer_device.h create mode 100644 components/knx/src/knx/network_layer_entity.cpp create mode 100644 components/knx/src/knx/network_layer_entity.h create mode 100644 components/knx/src/knx/npdu.cpp create mode 100644 components/knx/src/knx/npdu.h create mode 100644 components/knx/src/knx/platform.cpp create mode 100644 components/knx/src/knx/platform.h create mode 100644 components/knx/src/knx/property.cpp create mode 100644 components/knx/src/knx/property.h create mode 100644 components/knx/src/knx/rf_data_link_layer.cpp create mode 100644 components/knx/src/knx/rf_data_link_layer.h create mode 100644 components/knx/src/knx/rf_medium_object.cpp create mode 100644 components/knx/src/knx/rf_medium_object.h create mode 100644 components/knx/src/knx/rf_physical_layer.h create mode 100644 components/knx/src/knx/rf_physical_layer_cc1101.cpp create mode 100644 components/knx/src/knx/rf_physical_layer_cc1101.h create mode 100644 components/knx/src/knx/rf_physical_layer_cc1310.cpp create mode 100644 components/knx/src/knx/rf_physical_layer_cc1310.h create mode 100644 components/knx/src/knx/router_object.cpp create mode 100644 components/knx/src/knx/router_object.h create mode 100644 components/knx/src/knx/save_restore.h create mode 100644 components/knx/src/knx/secure_application_layer.cpp create mode 100644 components/knx/src/knx/secure_application_layer.h create mode 100644 components/knx/src/knx/security_interface_object.cpp create mode 100644 components/knx/src/knx/security_interface_object.h create mode 100644 components/knx/src/knx/service_families.h create mode 100644 components/knx/src/knx/simple_map.h create mode 100644 components/knx/src/knx/table_object.cpp create mode 100644 components/knx/src/knx/table_object.h create mode 100644 components/knx/src/knx/tp_frame.h create mode 100644 components/knx/src/knx/tpdu.cpp create mode 100644 components/knx/src/knx/tpdu.h create mode 100644 components/knx/src/knx/tpuart_data_link_layer.cpp create mode 100644 components/knx/src/knx/tpuart_data_link_layer.h create mode 100644 components/knx/src/knx/transport_layer.cpp create mode 100644 components/knx/src/knx/transport_layer.h create mode 100644 components/knx/src/knx/usb_tunnel_interface.cpp create mode 100644 components/knx/src/knx/usb_tunnel_interface.h create mode 100644 components/knx/src/knx_facade.cpp create mode 100644 components/knx/src/knx_facade.h create mode 100644 components/knx/src/libretiny_platform.cpp create mode 100644 components/knx/src/libretiny_platform.h create mode 100644 components/knx/src/linux_platform.cpp create mode 100644 components/knx/src/linux_platform.h create mode 100644 components/knx/src/rp2040_arduino_platform.cpp create mode 100644 components/knx/src/rp2040_arduino_platform.h create mode 100644 components/knx/src/samd_platform.cpp create mode 100644 components/knx/src/samd_platform.h create mode 100644 components/knx/src/stm32_platform.cpp create mode 100644 components/knx/src/stm32_platform.h create mode 100644 main/Display.cpp create mode 100644 main/Display.hpp create mode 100644 main/Gui.cpp create mode 100644 main/Gui.hpp create mode 100644 main/KnxWorker.cpp create mode 100644 main/KnxWorker.hpp create mode 100644 main/Nvs.cpp create mode 100644 main/Nvs.hpp create mode 100644 main/Touch.cpp create mode 100644 main/Touch.hpp delete mode 100644 main/hello_world_main.c create mode 100644 main/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cf1759..2ed3fca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,14 @@ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -# "Trim" the build. Include the minimal set of components, main, and anything it depends on. -# idf_build_set_property(MINIMAL_BUILD ON) + +add_definitions( + -Wno-unknown-pragmas + -DMASK_VERSION=0x07B0 + -DKNX_NO_AUTOMATIC_GLOBAL_INSTANCE + -DKNX_FLASH_SIZE=4096 + #-DKNX_NO_PRINT + #-Wno-stringop-truncation +) + project(knxdisplay) diff --git a/components/knx/CMakeLists.txt b/components/knx/CMakeLists.txt new file mode 100644 index 0000000..ed93739 --- /dev/null +++ b/components/knx/CMakeLists.txt @@ -0,0 +1,11 @@ +# Define the directory containing your source files +set(SOURCE_DIR "./src") +set(SOURCE_DIR_1 "./src/knx") + +# Use file(GLOB) to find all .cpp files in the 'src' directory +file(GLOB SOURCE_FILES "${SOURCE_DIR}/*.cpp") +file(GLOB SOURCE_FILES_1 "${SOURCE_DIR_1}/*.cpp") + +idf_component_register(SRCS ${SOURCE_FILES} ${SOURCE_FILES_1} + INCLUDE_DIRS "./src" "./src/knx" + REQUIRES esp_netif driver esp_timer esp_wifi freertos nvs_flash esp_system) \ No newline at end of file diff --git a/components/knx/src/arduino_platform.cpp b/components/knx/src/arduino_platform.cpp new file mode 100644 index 0000000..f9a9149 --- /dev/null +++ b/components/knx/src/arduino_platform.cpp @@ -0,0 +1,315 @@ +#ifdef ARDUINO + +#include "arduino_platform.h" +#include "knx/bits.h" + +#include +#ifndef KNX_NO_SPI + #include +#endif + +#ifndef KNX_NO_PRINT + Stream* ArduinoPlatform::SerialDebug = &KNX_DEBUG_SERIAL; +#endif + +ArduinoPlatform::ArduinoPlatform() : _knxSerial(nullptr) +{ +} + +ArduinoPlatform::ArduinoPlatform(HardwareSerial* knxSerial) : _knxSerial(knxSerial) +{ +} + +void ArduinoPlatform::fatalError() +{ + while (true) + { +#ifdef KNX_LED + static const long LED_BLINK_PERIOD = 200; + + if ((millis() % LED_BLINK_PERIOD) > (LED_BLINK_PERIOD / 2)) + digitalWrite(KNX_LED, HIGH); + else + digitalWrite(KNX_LED, LOW); + +#endif + } +} + +void ArduinoPlatform::knxUart( HardwareSerial* serial ) +{ + if (_knxSerial) + closeUart(); + + _knxSerial = serial; + setupUart(); +} + +HardwareSerial* ArduinoPlatform::knxUart() +{ + return _knxSerial; +} + +void ArduinoPlatform::setupUart() +{ + _knxSerial->begin(19200, SERIAL_8E1); + + while (!_knxSerial) + ; +} + + +void ArduinoPlatform::closeUart() +{ + _knxSerial->end(); +} + + +int ArduinoPlatform::uartAvailable() +{ + return _knxSerial->available(); +} + + +size_t ArduinoPlatform::writeUart(const uint8_t data) +{ + //printHex("write(data); +} + + +size_t ArduinoPlatform::writeUart(const uint8_t* buffer, size_t size) +{ + //printHex("write(buffer, size); +} + + +int ArduinoPlatform::readUart() +{ + int val = _knxSerial->read(); + //if(val > 0) + // printHex("p>", (uint8_t*)&val, 1); + return val; +} + + +size_t ArduinoPlatform::readBytesUart(uint8_t* buffer, size_t length) +{ + size_t toRead = length; + uint8_t* pos = buffer; + + while (toRead > 0) + { + size_t val = _knxSerial->readBytes(pos, toRead); + pos += val; + toRead -= val; + } + + //printHex("p>", buffer, length); + return length; +} + +void ArduinoPlatform::flushUart() +{ + return _knxSerial->flush(); +} + +#ifndef KNX_NO_SPI +void ArduinoPlatform::setupSpi() +{ + SPI.begin(); + SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); +} + +void ArduinoPlatform::closeSpi() +{ + SPI.endTransaction(); + SPI.end(); +} + +int ArduinoPlatform::readWriteSpi(uint8_t* data, size_t len) +{ + SPI.transfer(data, len); + return 0; +} +#endif + +#ifndef KNX_NO_PRINT +void printUint64(uint64_t value, int base = DEC) +{ + char buf[8 * sizeof(uint64_t) + 1]; + char* str = &buf[sizeof(buf) - 1]; + *str = '\0'; + + uint64_t n = value; + + do + { + char c = n % base; + n /= base; + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while (n > 0); + + print(str); +} + +void print(const char* s) +{ + ArduinoPlatform::SerialDebug->print(s); +} +void print(char c) +{ + ArduinoPlatform::SerialDebug->print(c); +} + +void print(unsigned char num) +{ + ArduinoPlatform::SerialDebug->print(num); +} + +void print(unsigned char num, int base) +{ + ArduinoPlatform::SerialDebug->print(num, base); +} + +void print(int num) +{ + ArduinoPlatform::SerialDebug->print(num); +} + +void print(int num, int base) +{ + ArduinoPlatform::SerialDebug->print(num, base); +} + +void print(unsigned int num) +{ + ArduinoPlatform::SerialDebug->print(num); +} + +void print(unsigned int num, int base) +{ + ArduinoPlatform::SerialDebug->print(num, base); +} + +void print(long num) +{ + ArduinoPlatform::SerialDebug->print(num); +} + +void print(long num, int base) +{ + ArduinoPlatform::SerialDebug->print(num, base); +} + +void print(unsigned long num) +{ + ArduinoPlatform::SerialDebug->print(num); +} + +void print(unsigned long num, int base) +{ + ArduinoPlatform::SerialDebug->print(num, base); +} + +void print(unsigned long long num) +{ + printUint64(num); +} + +void print(unsigned long long num, int base) +{ + printUint64(num, base); +} + +void print(double num) +{ + ArduinoPlatform::SerialDebug->print(num); +} + +void println(const char* s) +{ + ArduinoPlatform::SerialDebug->println(s); +} + +void println(char c) +{ + ArduinoPlatform::SerialDebug->println(c); +} + +void println(unsigned char num) +{ + ArduinoPlatform::SerialDebug->println(num); +} + +void println(unsigned char num, int base) +{ + ArduinoPlatform::SerialDebug->println(num, base); +} + +void println(int num) +{ + ArduinoPlatform::SerialDebug->println(num); +} + +void println(int num, int base) +{ + ArduinoPlatform::SerialDebug->println(num, base); +} + +void println(unsigned int num) +{ + ArduinoPlatform::SerialDebug->println(num); +} + +void println(unsigned int num, int base) +{ + ArduinoPlatform::SerialDebug->println(num, base); +} + +void println(long num) +{ + ArduinoPlatform::SerialDebug->println(num); +} + +void println(long num, int base) +{ + ArduinoPlatform::SerialDebug->println(num, base); +} + +void println(unsigned long num) +{ + ArduinoPlatform::SerialDebug->println(num); +} + +void println(unsigned long num, int base) +{ + ArduinoPlatform::SerialDebug->println(num, base); +} + +void println(unsigned long long num) +{ + printUint64(num); + println(""); +} + +void println(unsigned long long num, int base) +{ + printUint64(num, base); + println(""); +} + +void println(double num) +{ + ArduinoPlatform::SerialDebug->println(num); +} + +void println(void) +{ + ArduinoPlatform::SerialDebug->println(); +} +#endif // KNX_NO_PRINT + +#endif // ARDUINO \ No newline at end of file diff --git a/components/knx/src/arduino_platform.h b/components/knx/src/arduino_platform.h new file mode 100644 index 0000000..24447dc --- /dev/null +++ b/components/knx/src/arduino_platform.h @@ -0,0 +1,47 @@ +#pragma once + +#ifdef ARDUINO + +#include "knx/platform.h" + +#include "Arduino.h" + +#ifndef KNX_DEBUG_SERIAL + #define KNX_DEBUG_SERIAL Serial +#endif + +class ArduinoPlatform : public Platform +{ + public: + ArduinoPlatform(); + ArduinoPlatform(HardwareSerial* knxSerial); + + // basic stuff + void fatalError(); + + //uart + virtual void knxUart( HardwareSerial* serial); + virtual HardwareSerial* knxUart(); + virtual void setupUart(); + virtual void closeUart(); + virtual int uartAvailable(); + virtual size_t writeUart(const uint8_t data); + virtual size_t writeUart(const uint8_t* buffer, size_t size); + virtual int readUart(); + virtual size_t readBytesUart(uint8_t* buffer, size_t length); + virtual void flushUart(); + + //spi +#ifndef KNX_NO_SPI + void setupSpi() override; + void closeSpi() override; + int readWriteSpi (uint8_t* data, size_t len) override; +#endif +#ifndef KNX_NO_PRINT + static Stream* SerialDebug; +#endif + + protected: + HardwareSerial* _knxSerial; +}; +#endif // ARDUINO \ No newline at end of file diff --git a/components/knx/src/cc1310_platform.cpp b/components/knx/src/cc1310_platform.cpp new file mode 100644 index 0000000..3266927 --- /dev/null +++ b/components/knx/src/cc1310_platform.cpp @@ -0,0 +1,587 @@ +#ifdef DeviceFamily_CC13X0 + +#include +#include +#include +#include + +#include +#include DeviceFamily_constructPath(driverlib/sys_ctrl.h) +#include + +#include "SEGGER_RTT.h" + +#include "Board.h" + +#include "knx/bits.h" +#include "cc1310_platform.h" + +//#define printf(args...) (SEGGER_RTT_printf(0, args)) +//#define PRINT_RTT +#define PRINT_UART + +static uint8_t serialNumber[6]; +// KNX_FLASH_SIZE shall be defined in CMakeLists.txt for example. It is also used in class Memory in memory.cpp +static uint8_t NVS_buffer[KNX_FLASH_SIZE]; + +static UART_Handle uart; + +static NVS_Handle nvsHandle; + +static ClockP_Handle clk0Handle; +static ClockP_Struct clk0Struct; +static volatile uint32_t msCounter = 0; + +static void clk0Fxn(uintptr_t arg0) +{ + msCounter++; +} + +static void setupClock() +{ + ClockP_Params clkParams; + ClockP_Params_init(&clkParams); + clkParams.period = 1000 / ClockP_tickPeriod; + clkParams.startFlag = true; + ClockP_construct(&clk0Struct, (ClockP_Fxn)clk0Fxn, 1000 / ClockP_tickPeriod, &clkParams); + clk0Handle = ClockP_handle(&clk0Struct); +} + +static void setupGPIO() +{ + /* Configure the LED and button pins */ + GPIO_setConfig(Board_GPIO_LED0, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW); + GPIO_setConfig(Board_GPIO_LED1, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW); + GPIO_setConfig(Board_GPIO_BUTTON0, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_FALLING); + GPIO_setConfig(Board_GPIO_BUTTON1, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_FALLING); +} + +static void setupUART() +{ + UART_Params uartParams; + UART_Params_init(&uartParams); + uartParams.writeDataMode = UART_DATA_BINARY; + uartParams.readDataMode = UART_DATA_BINARY; + uartParams.readReturnMode = UART_RETURN_FULL; + uartParams.readEcho = UART_ECHO_OFF; + uartParams.baudRate = 115200; + uart = UART_open(Board_UART0, &uartParams); + + if (uart == NULL) + { + while (true) + {} + } +} + +static void setupNVS() +{ + NVS_Params nvsParams; + NVS_Params_init(&nvsParams); + nvsHandle = NVS_open(Board_NVSINTERNAL, &nvsParams); + + if (nvsHandle == NULL) + { + println("NVS_open() failed."); + return; + } + + NVS_Attrs attrs; + NVS_getAttrs(nvsHandle, &attrs); + print("NVS flash size: "); + println((int)attrs.regionSize); + print("NVS flash sector size: "); + println((int)attrs.sectorSize); + + if (GPIO_read(Board_GPIO_BUTTON1) == 0) + { + println("Button1 is pressed. Erasing flash..."); + int_fast16_t result = NVS_erase(nvsHandle, 0, attrs.regionSize); + + if (result != NVS_STATUS_SUCCESS) + { + print("Error erasing NVS, result: "); + println(result); + } + else + { + println("NVS successfully erased."); + } + } +} + +void sleep(uint32_t sec) +{ + ClockP_sleep(sec); +} + +void usleep(uint32_t usec) +{ + ClockP_usleep(usec); +} + +uint32_t millis() +{ + // we use our own ms clock because the Os tick counter has counts 10us ticks and following calculation would not wrap correctly at 32bit boundary + //return Clock_getTicks() * (uint64_t) Clock_tickPeriod / 1000; // rtos + //return ClockP_getTicks( * (uint64_t) Clock_tickPeriod / 1000); //nortos + return msCounter; +} + +void delay(uint32_t ms) +{ + ClockP_usleep(ms * 1000); + //sleep(ms * (1000 / ClockP_tickPeriod)); //rtos + //sleepTicks(millis * 1000ULL / ClockP_tickPeriod); //nortos +} + +void delayMicroseconds (unsigned int howLong) +{ + ClockP_usleep(howLong); +} + +#ifndef KNX_NO_PRINT +size_t write(uint8_t c) +{ +#if defined(PRINT_UART) + uint8_t buffer[1] = {c}; + return UART_write(uart, buffer, sizeof(buffer)); +#elif defined (PRINT_RTT) + return SEGGER_RTT_PutChar(0, (char)c); +#else + return 1; +#endif +} + +#if 0 +size_t write(const uint8_t* buffer, size_t size) +{ + size_t n = 0; + + while (size--) + { + if (write(*buffer++)) + { + n++; + } + else + { + break; + } + } + + return n; +} +#else +size_t write(const uint8_t* buffer, size_t size) +{ +#if defined(PRINT_UART) + return UART_write(uart, buffer, size); +#elif defined (PRINT_RTT) + return SEGGER_RTT_Write(0, buffer, size); +#else + return size; +#endif +} +#endif + +size_t write(const char* buffer, size_t size) +{ + return write((const uint8_t*)buffer, size); +} + +void print(const char* s) +{ + if (s == NULL) + { + return; + } + + write(s, strlen(s)); +} +void print(char c) +{ + write(c); +} + +void printUint64(uint64_t value, int base = DEC) +{ + char buf[8 * sizeof(uint64_t) + 1]; + char* str = &buf[sizeof(buf) - 1]; + *str = '\0'; + + uint64_t n = value; + + do + { + char c = n % base; + n /= base; + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while (n > 0); + + print(str); +} + +void print(long long num, int base) +{ + if (base == 0) + { + write(num); + return; + } + else if (base == 10) + { + if (num < 0) + { + print('-'); + num = -num; + printUint64(num, 10); + return; + } + + printUint64(num, 10); + return; + } + else + { + printUint64(num, base); + return; + } +} + +void print(unsigned long long num, int base) +{ + if (base == 0) + { + write(num); + return; + } + else + { + printUint64(num, base); + return; + } +} + +void print(unsigned char num, int base) +{ + print((unsigned long long)num, base); +} + +void print(int num, int base) +{ + print((long long)num, base); +} + +void print(unsigned int num, int base) +{ + print((unsigned long long)num, base); +} + +void print(long num, int base) +{ + print((long long)num, base); +} + +void print(unsigned long num, int base) +{ + print((unsigned long long)num, base); +} + +void printFloat(double number, uint8_t digits) +{ + if (std::isnan(number)) + { + print("nan"); + return; + } + + if (std::isinf(number)) + { + print("inf"); + return; + } + + if (number > 4294967040.0) + { + print("ovf"); // constant determined empirically + return; + } + + if (number < -4294967040.0) + { + print("ovf"); // constant determined empirically + return; + } + + // Handle negative numbers + if (number < 0.0) + { + print('-'); + number = -number; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + + for (uint8_t i = 0; i < digits; ++i) + rounding /= 10.0; + + number += rounding; + + // Extract the integer part of the number and print it + unsigned long int_part = (unsigned long)number; + double remainder = number - (double)int_part; + printUint64(int_part); + + // Print the decimal point, but only if there are digits beyond + if (digits > 0) + { + print('.'); + } + + // Extract digits from the remainder one at a time + while (digits-- > 0) + { + remainder *= 10.0; + unsigned int toPrint = (unsigned int)(remainder); + printUint64(toPrint); + remainder -= toPrint; + } +} + +void print(double num, int digits = 2) +{ + printFloat(num, digits); +} + +void println(void) +{ + print("\r\n"); +} + +void println(const char* s) +{ + print(s); + println(); +} +void println(char c) +{ + print(c); + println(); +} + +void println(unsigned char num, int base) +{ + print(num, base); + println(); +} + +void println(int num, int base) +{ + print(num, base); + println(); +} + +void println(unsigned int num, int base) +{ + print(num, base); + println(); +} + +void println(long num, int base) +{ + print(num, base); + println(); +} + +void println(unsigned long num, int base) +{ + print(num, base); + println(); +} + +void println(unsigned long long num, int base) +{ + printUint64(num, base); + println(); +} + +void println(double num, int digits = 2) +{ + print(num, digits); + println(); +} + +void println(double num) +{ + // default: print 10 digits + println(num, 10); +} +#endif // KNX_NO_PRINT + +uint32_t digitalRead(uint32_t dwPin) +{ + print("ignoring digitalRead: pin: "); + print(dwPin); + println(", returning 0"); + return 0; +} + +void digitalWrite(unsigned long pin, unsigned long value) +{ + if (pin == Board_GPIO_LED0) + { + if (value > 0) + { + GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_ON); + } + else + { + GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_OFF); + } + } + else + { + print("dummy digitalWrite: pin: "); + print(pin); + print(", value: "); + println(value, HEX); + } +} + +void pinMode(unsigned long pin, unsigned long mode) +{ + print("ignoring pinMode: pin: "); + print(pin); + print(", mode: "); + println(mode, HEX); +} + +typedef void (*IsrFuncPtr)(); +static IsrFuncPtr gpioCallback; +static void gpioButtonFxn0(uint_least8_t index) +{ + gpioCallback(); +} + +void attachInterrupt(uint32_t pin, IsrFuncPtr callback, uint32_t mode) +{ + if (pin == Board_GPIO_BUTTON0) + { + gpioCallback = callback; + /* install Button callback */ + GPIO_setCallback(Board_GPIO_BUTTON0, gpioButtonFxn0); + + /* Enable interrupts */ + GPIO_enableInt(Board_GPIO_BUTTON0); + } + else + { + print("dummy attachInterrupt: pin: "); + print(pin); + print(", mode: "); + println(mode, HEX); + } +} + +CC1310Platform::CC1310Platform() +{ + // build serialNumber from IEEE MAC Address (MAC is 8 bytes, serialNumber 6 bytes only) + *(uint32_t*)(serialNumber + 2) = HWREG(FCFG1_BASE + FCFG1_O_MAC_15_4_0) ^ HWREG(FCFG1_BASE + FCFG1_O_MAC_15_4_1); // make a 6 byte hash from 8 bytes +} + +CC1310Platform::~CC1310Platform() +{ +} + +void CC1310Platform::init() +{ + // TI Drivers init + // According to SDK docs it is safe to call them AFTER NoRTOS_Start() + // If RTOS is used and multiple thread use the same driver, then the init shall be performed before BIOS_Start() + GPIO_init(); + UART_init(); + NVS_init(); + + // Init GPIO + setupGPIO(); + + // Init UART + setupUART(); + + // tick Period on this controller 10us so we use our own millisecond clock + setupClock(); + + // Init flash + setupNVS(); +} + +uint8_t* CC1310Platform::getEepromBuffer(uint32_t size) +{ + if (size > KNX_FLASH_SIZE) + { + fatalError(); + } + + NVS_read(nvsHandle, 0, (void*) NVS_buffer, size); + + for (int i = 0; i < size; i++) + { + if (NVS_buffer[i] != 0) + { + return NVS_buffer; + } + } + + memset(NVS_buffer, 0xff, size); + + return NVS_buffer; +} + +void CC1310Platform::commitToEeprom() +{ + println("CC1310Platform::commitToEeprom() ..."); + + int_fast16_t result = NVS_write(nvsHandle, 0, (void*)NVS_buffer, KNX_FLASH_SIZE, NVS_WRITE_ERASE | NVS_WRITE_POST_VERIFY); + + if (result != NVS_STATUS_SUCCESS) + { + print("Error writing to NVS, result: "); + println(result); + } + else + { + println("NVS successfully written"); + } + + delay(500); +} + +void CC1310Platform::restart() +{ + println("System restart in 500ms."); + delay(500); + SysCtrlSystemReset(); + // Should neber be reached! + fatalError(); +} + +void CC1310Platform::fatalError() +{ + println("A fatal error occured. Stopped."); + + while (true) + { + /* Turn on user LED */ + GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_OFF); + GPIO_write(Board_GPIO_LED1, Board_GPIO_LED_ON); + delay(500); + GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_ON); + GPIO_write(Board_GPIO_LED1, Board_GPIO_LED_OFF); + delay(500); + } +} + +#endif // DeviceFamily_CC13X0 diff --git a/components/knx/src/cc1310_platform.h b/components/knx/src/cc1310_platform.h new file mode 100644 index 0000000..4289077 --- /dev/null +++ b/components/knx/src/cc1310_platform.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef DeviceFamily_CC13X0 + +#include +#include +#include + +#include "knx/platform.h" + +class CC1310Platform : public Platform +{ + public: + CC1310Platform(); + virtual ~CC1310Platform(); + + void init(); + + // basic stuff + void restart() final; + void fatalError() final; + + uint8_t* getEepromBuffer(uint32_t size) final; + void commitToEeprom() final; +}; + +#endif //DeviceFamily_CC13X0 diff --git a/components/knx/src/esp32_idf_platform.cpp b/components/knx/src/esp32_idf_platform.cpp new file mode 100644 index 0000000..99da209 --- /dev/null +++ b/components/knx/src/esp32_idf_platform.cpp @@ -0,0 +1,443 @@ +#ifndef ARDUINO +#ifdef ESP_PLATFORM +// esp32_idf_platform.cpp +#include +#include +#include "esp32_idf_platform.h" +#include "esp_log.h" +#include "knx/bits.h" +#include "nvs.h" +#include + +static const char* KTAG = "KNX_LIB"; + +Esp32IdfPlatform::Esp32IdfPlatform(uart_port_t uart_num) + : _uart_num(uart_num) +{ + // Set the memory type to use our NVS-based EEPROM emulation + _memoryType = Eeprom; +} + +Esp32IdfPlatform::~Esp32IdfPlatform() +{ + if (_sock != -1) + { + closeMultiCast(); + } + if (_uart_installed) + { + closeUart(); + } + if (_eeprom_buffer) + { + free(_eeprom_buffer); + } + if (_nvs_handle) + { + nvs_close(_nvs_handle); + } +} + +void Esp32IdfPlatform::knxUartPins(int8_t rxPin, int8_t txPin) +{ + _rxPin = rxPin; + _txPin = txPin; +} + +void Esp32IdfPlatform::knxUartBaudRate(uint32_t baudRate) +{ + _baudRate = baudRate; + ESP_LOGI(KTAG, "UART baud rate set to %lu", _baudRate); +} + +void Esp32IdfPlatform::setNetif(esp_netif_t* netif) +{ + _netif = netif; +} + +void Esp32IdfPlatform::fatalError() +{ + ESP_LOGE(KTAG, "FATAL ERROR. System halted."); + // Loop forever to halt the system + while (1) + { + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} + +// ESP specific uart handling with pins +void Esp32IdfPlatform::setupUart() +{ + if (_uart_installed) + return; + uart_config_t uart_config; + memset(&uart_config, 0, sizeof(uart_config)); + uart_config.baud_rate = _baudRate; // Use configurable baud rate + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_EVEN; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_DEFAULT; + + ESP_ERROR_CHECK(uart_driver_install(_uart_num, 256 * 2, 0, 0, NULL, 0)); + ESP_ERROR_CHECK(uart_param_config(_uart_num, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(_uart_num, _txPin, _rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + _uart_installed = true; + ESP_LOGI(KTAG, "UART initialized with baud rate %lu", _baudRate); +} + +void Esp32IdfPlatform::closeUart() +{ + if (!_uart_installed) + return; + uart_driver_delete(_uart_num); + _uart_installed = false; +} + +int Esp32IdfPlatform::uartAvailable() +{ + if (!_uart_installed) + return 0; + size_t length = 0; + ESP_ERROR_CHECK(uart_get_buffered_data_len(_uart_num, &length)); + return length; +} + +size_t Esp32IdfPlatform::writeUart(const uint8_t data) +{ + if (!_uart_installed) + return 0; + return uart_write_bytes(_uart_num, &data, 1); +} + +size_t Esp32IdfPlatform::writeUart(const uint8_t* buffer, size_t size) +{ + if (!_uart_installed) + return 0; + return uart_write_bytes(_uart_num, buffer, size); +} + +int Esp32IdfPlatform::readUart() +{ + if (!_uart_installed) + return -1; + uint8_t data; + if (uart_read_bytes(_uart_num, &data, 1, pdMS_TO_TICKS(20)) > 0) + { + return data; + } + return -1; +} + +size_t Esp32IdfPlatform::readBytesUart(uint8_t* buffer, size_t length) +{ + if (!_uart_installed) + return 0; + return uart_read_bytes(_uart_num, buffer, length, pdMS_TO_TICKS(100)); +} + +void Esp32IdfPlatform::flushUart() +{ + if (!_uart_installed) + return; + ESP_ERROR_CHECK(uart_flush(_uart_num)); +} + +uint32_t Esp32IdfPlatform::currentIpAddress() +{ + if (!_netif) + return 0; + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(_netif, &ip_info); + return ip_info.ip.addr; +} + +uint32_t Esp32IdfPlatform::currentSubnetMask() +{ + if (!_netif) + return 0; + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(_netif, &ip_info); + return ip_info.netmask.addr; +} + +uint32_t Esp32IdfPlatform::currentDefaultGateway() +{ + if (!_netif) + return 0; + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(_netif, &ip_info); + return ip_info.gw.addr; +} + +void Esp32IdfPlatform::macAddress(uint8_t* addr) +{ + if (!_netif) + return; + esp_netif_get_mac(_netif, addr); +} + +uint32_t Esp32IdfPlatform::uniqueSerialNumber() +{ + uint8_t mac[6]; + esp_efuse_mac_get_default(mac); + uint64_t chipid = 0; + for (int i = 0; i < 6; i++) + { + chipid |= ((uint64_t)mac[i] << (i * 8)); + } + uint32_t upperId = (chipid >> 32) & 0xFFFFFFFF; + uint32_t lowerId = (chipid & 0xFFFFFFFF); + return (upperId ^ lowerId); +} + +void Esp32IdfPlatform::restart() +{ + ESP_LOGI(KTAG, "Restarting system..."); + esp_restart(); +} + +void Esp32IdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) +{ + _multicast_addr = addr; + _multicast_port = port; + + _sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (_sock < 0) + { + ESP_LOGE(KTAG, "Failed to create socket. Errno: %d", errno); + return; + } + + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_port = htons(port); + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(_sock, (struct sockaddr*)&saddr, sizeof(struct sockaddr_in)) < 0) + { + ESP_LOGE(KTAG, "Failed to bind socket. Errno: %d", errno); + close(_sock); + _sock = -1; + return; + } + + struct ip_mreq imreq; + memset(&imreq, 0, sizeof(imreq)); + imreq.imr_interface.s_addr = IPADDR_ANY; + imreq.imr_multiaddr.s_addr = addr; + if (setsockopt(_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(struct ip_mreq)) < 0) + { + ESP_LOGE(KTAG, "Failed to join multicast group. Errno: %d", errno); + close(_sock); + _sock = -1; + return; + } + + ESP_LOGI(KTAG, "Successfully joined multicast group on port %d", port); +} + +void Esp32IdfPlatform::closeMultiCast() +{ + if (_sock != -1) + { + close(_sock); + _sock = -1; + } +} + +bool Esp32IdfPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) +{ + if (_sock < 0) + return false; + + struct sockaddr_in dest_addr = {}; + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(_multicast_port); + dest_addr.sin_addr.s_addr = _multicast_addr; + + int sent_len = sendto(_sock, buffer, len, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); + if (sent_len < 0) + { + ESP_LOGE(KTAG, "sendBytesMultiCast failed. Errno: %d", errno); + return false; + } + return sent_len == len; +} + +int Esp32IdfPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) +{ + if (_sock < 0) + return 0; + + socklen_t socklen = sizeof(_remote_addr); + int len = recvfrom(_sock, buffer, maxLen, 0, (struct sockaddr*)&_remote_addr, &socklen); + + if (len <= 0) + { + return 0; // No data or error + } + + src_addr = _remote_addr.sin_addr.s_addr; + src_port = ntohs(_remote_addr.sin_port); + + return len; +} + +bool Esp32IdfPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) +{ + if (_sock < 0) + return false; + + struct sockaddr_in dest_addr; + dest_addr.sin_family = AF_INET; + + if (addr == 0) + { // If address is 0, use the address from the last received packet + dest_addr.sin_addr.s_addr = _remote_addr.sin_addr.s_addr; + } + else + { + dest_addr.sin_addr.s_addr = addr; + } + + if (port == 0) + { // If port is 0, use the port from the last received packet + dest_addr.sin_port = _remote_addr.sin_port; + } + else + { + dest_addr.sin_port = htons(port); + } + + if (sendto(_sock, buffer, len, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)) < 0) + { + ESP_LOGE(KTAG, "sendBytesUniCast failed. Errno: %d", errno); + return false; + } + + return true; +} + +uint8_t* Esp32IdfPlatform::getEepromBuffer(uint32_t size) +{ + if (_eeprom_buffer && _eeprom_size == size) + { + return _eeprom_buffer; + } + + if (_eeprom_buffer) + { + free(_eeprom_buffer); + _eeprom_buffer = nullptr; + } + + _eeprom_size = size; + _eeprom_buffer = (uint8_t*)malloc(size); + if (!_eeprom_buffer) + { + ESP_LOGE(KTAG, "Failed to allocate EEPROM buffer"); + fatalError(); + return nullptr; + } + + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + err = nvs_open(_nvs_namespace, NVS_READWRITE, &_nvs_handle); + if (err != ESP_OK) + { + ESP_LOGE(KTAG, "Error opening NVS handle: %s", esp_err_to_name(err)); + free(_eeprom_buffer); + _eeprom_buffer = nullptr; + fatalError(); + return nullptr; + } + + size_t required_size = size; + err = nvs_get_blob(_nvs_handle, _nvs_key, _eeprom_buffer, &required_size); + if (err != ESP_OK || required_size != size) + { + if (err == ESP_ERR_NVS_NOT_FOUND) + { + ESP_LOGI(KTAG, "No previous EEPROM data found in NVS. Initializing fresh buffer."); + } + else + { + ESP_LOGW(KTAG, "NVS get blob failed (%s) or size mismatch. Initializing fresh buffer.", esp_err_to_name(err)); + } + memset(_eeprom_buffer, 0xFF, size); + } + else + { + ESP_LOGI(KTAG, "Successfully loaded %d bytes from NVS into EEPROM buffer.", required_size); + } + + return _eeprom_buffer; +} + +void Esp32IdfPlatform::commitToEeprom() +{ + if (!_eeprom_buffer || !_nvs_handle) + { + ESP_LOGE(KTAG, "EEPROM not initialized, cannot commit."); + return; + } + + esp_err_t err = nvs_set_blob(_nvs_handle, _nvs_key, _eeprom_buffer, _eeprom_size); + if (err != ESP_OK) + { + ESP_LOGE(KTAG, "Failed to set NVS blob: %s", esp_err_to_name(err)); + return; + } + + err = nvs_commit(_nvs_handle); + if (err != ESP_OK) + { + ESP_LOGE(KTAG, "Failed to commit NVS: %s", esp_err_to_name(err)); + } + else + { + ESP_LOGI(KTAG, "Committed %" PRIu32 " bytes to NVS.", _eeprom_size); + } +} + +uint32_t millis() +{ + // esp_timer_get_time() returns microseconds, so we divide by 1000 for milliseconds. + // Cast to uint32_t to match the Arduino function signature. + return (uint32_t)(esp_timer_get_time() / 1000); +} + +// Internal wrapper function to bridge Arduino-style ISR to ESP-IDF +static void IRAM_ATTR isr_wrapper(void* arg) +{ + IsrFuncPtr fn = (IsrFuncPtr)arg; + fn(); // call the original ISR +} + +// Implement attachInterrupt arduino like in ESP IDF +void attachInterrupt(uint32_t pin, IsrFuncPtr callback, uint32_t mode) +{ + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << pin), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = (gpio_int_type_t)mode + }; + + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + ESP_ERROR_CHECK(gpio_install_isr_service(0)); + // Add ISR using the wrapper and pass original function as argument + ESP_ERROR_CHECK(gpio_isr_handler_add((gpio_num_t)pin, isr_wrapper, (void*)callback)); +} + +#endif // ESP_PLATFORM +#endif // !ARDUINO \ No newline at end of file diff --git a/components/knx/src/esp32_idf_platform.h b/components/knx/src/esp32_idf_platform.h new file mode 100644 index 0000000..58f83cf --- /dev/null +++ b/components/knx/src/esp32_idf_platform.h @@ -0,0 +1,89 @@ +#ifndef ARDUINO +#ifdef ESP_PLATFORM +// esp_idf_platform.h +#pragma once + +#include "driver/uart.h" +#include "esp_netif.h" +#include "esp_system.h" +#include "lwip/sockets.h" +#include "nvs_flash.h" +#include "knx/platform.h"// Include the provided base class + +class Esp32IdfPlatform : public Platform +{ + public: + Esp32IdfPlatform(uart_port_t uart_num = UART_NUM_1); + ~Esp32IdfPlatform(); + + // uart + void knxUartPins(int8_t rxPin, int8_t txPin); + void knxUartBaudRate(uint32_t baudRate); // Add baud rate configuration + + // Call this after WiFi/Ethernet has started and received an IP. + void setNetif(esp_netif_t* netif); + + // --- Overridden Virtual Functions --- + + // ip stuff + uint32_t currentIpAddress() override; + uint32_t currentSubnetMask() override; + uint32_t currentDefaultGateway() override; + void macAddress(uint8_t* addr) override; + + // unique serial number + uint32_t uniqueSerialNumber() override; + + // basic stuff (pure virtual in base) + void restart() override; + void fatalError() override; + + // multicast + void setupMultiCast(uint32_t addr, uint16_t port) override; + void closeMultiCast() override; + bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) override; + + // unicast + bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; + + // UART + void setupUart() override; + void closeUart() override; + int uartAvailable() override; + size_t writeUart(const uint8_t data) override; + size_t writeUart(const uint8_t* buffer, size_t size) override; + int readUart() override; + size_t readBytesUart(uint8_t* buffer, size_t length) override; + void flushUart() override; + + // Memory (EEPROM emulation via NVS) + // We override these two functions to provide the low-level storage mechanism. + // The base Platform class will use them when _memoryType is Eeprom. + uint8_t* getEepromBuffer(uint32_t size) override; + void commitToEeprom() override; + + private: + // Network + esp_netif_t* _netif = nullptr; + int _sock = -1; + struct sockaddr_in _remote_addr; + uint32_t _multicast_addr = 0; + uint16_t _multicast_port = 0; + + // UART + uart_port_t _uart_num; + int8_t _rxPin = -1; + int8_t _txPin = -1; + uint32_t _baudRate = 19200; // Default baud rate, can be changed + bool _uart_installed = false; + + // NVS (for EEPROM emulation) + nvs_handle_t _nvs_handle; + uint8_t* _eeprom_buffer = nullptr; + uint32_t _eeprom_size = 0; + const char* _nvs_namespace = "eeprom"; + const char* _nvs_key = "eeprom"; +}; +#endif +#endif \ No newline at end of file diff --git a/components/knx/src/esp32_platform.cpp b/components/knx/src/esp32_platform.cpp new file mode 100644 index 0000000..2f0bfd9 --- /dev/null +++ b/components/knx/src/esp32_platform.cpp @@ -0,0 +1,184 @@ +#include "esp32_platform.h" + +#ifdef ARDUINO_ARCH_ESP32 +#include +#include + +#include "knx/bits.h" + +#ifndef KNX_SERIAL + #define KNX_SERIAL Serial1 + #pragma warn "KNX_SERIAL not defined, using Serial1" +#endif + +#ifdef KNX_IP_LAN + #include "ETH.h" + #define KNX_NETIF ETH +#else // KNX_IP_WIFI + #include + #define KNX_NETIF WiFi +#endif + +Esp32Platform::Esp32Platform() +#ifndef KNX_NO_DEFAULT_UART + : ArduinoPlatform(&KNX_SERIAL) +#endif +{ +#ifdef KNX_UART_RX_PIN + _rxPin = KNX_UART_RX_PIN; +#endif +#ifdef KNX_UART_TX_PIN + _txPin = KNX_UART_TX_PIN; +#endif +} + +Esp32Platform::Esp32Platform(HardwareSerial* s) : ArduinoPlatform(s) +{ +} + +void Esp32Platform::knxUartPins(int8_t rxPin, int8_t txPin) +{ + _rxPin = rxPin; + _txPin = txPin; +} + +// ESP specific uart handling with pins +void Esp32Platform::setupUart() +{ + _knxSerial->begin(19200, SERIAL_8E1, _rxPin, _txPin); + + while (!_knxSerial) + ; +} + +uint32_t Esp32Platform::currentIpAddress() +{ + return KNX_NETIF.localIP(); +} + +uint32_t Esp32Platform::currentSubnetMask() +{ + return KNX_NETIF.subnetMask(); +} + +uint32_t Esp32Platform::currentDefaultGateway() +{ + return KNX_NETIF.gatewayIP(); +} + +void Esp32Platform::macAddress(uint8_t* addr) +{ + KNX_NETIF.macAddress(addr); +} + +uint32_t Esp32Platform::uniqueSerialNumber() +{ + uint64_t chipid = ESP.getEfuseMac(); + uint32_t upperId = (chipid >> 32) & 0xFFFFFFFF; + uint32_t lowerId = (chipid & 0xFFFFFFFF); + return (upperId ^ lowerId); +} + +void Esp32Platform::restart() +{ + println("restart"); + ESP.restart(); +} + +void Esp32Platform::setupMultiCast(uint32_t addr, uint16_t port) +{ + IPAddress mcastaddr(htonl(addr)); + + KNX_DEBUG_SERIAL.printf("setup multicast addr: %s port: %d ip: %s\n", mcastaddr.toString().c_str(), port, + KNX_NETIF.localIP().toString().c_str()); + uint8_t result = _udp.beginMulticast(mcastaddr, port); + KNX_DEBUG_SERIAL.printf("multicast setup result %d\n", result); +} + +void Esp32Platform::closeMultiCast() +{ + _udp.stop(); +} + +bool Esp32Platform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) +{ + //printHex("<- ",buffer, len); + _udp.beginMulticastPacket(); + _udp.write(buffer, len); + _udp.endPacket(); + return true; +} + +int Esp32Platform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) +{ + int len = _udp.parsePacket(); + + if (len == 0) + return 0; + + if (len > maxLen) + { + println("Unexpected UDP data packet length - drop packet"); + + for (size_t i = 0; i < len; i++) + _udp.read(); + + return 0; + } + + _udp.read(buffer, len); + _remoteIP = _udp.remoteIP(); + _remotePort = _udp.remotePort(); + src_addr = htonl(_remoteIP); + src_port = _remotePort; + + // print("Remote IP: "); + // print(_udp.remoteIP().toString().c_str()); + // printHex("-> ", buffer, len); + + return len; +} + +bool Esp32Platform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) +{ + IPAddress ucastaddr(htonl(addr)); + + if (!addr) + ucastaddr = _remoteIP; + + if (!port) + port = _remotePort; + + if (_udp.beginPacket(ucastaddr, port) == 1) + { + _udp.write(buffer, len); + + if (_udp.endPacket() == 0) + println("sendBytesUniCast endPacket fail"); + } + else + println("sendBytesUniCast beginPacket fail"); + + return true; +} + +uint8_t* Esp32Platform::getEepromBuffer(uint32_t size) +{ + uint8_t* eepromptr = EEPROM.getDataPtr(); + + if (eepromptr == nullptr) + { + EEPROM.begin(size); + eepromptr = EEPROM.getDataPtr(); + } + + return eepromptr; +} + +void Esp32Platform::commitToEeprom() +{ + EEPROM.getDataPtr(); // trigger dirty flag in EEPROM lib to make sure data will be written to flash + EEPROM.commit(); +} + +#endif diff --git a/components/knx/src/esp32_platform.h b/components/knx/src/esp32_platform.h new file mode 100644 index 0000000..3ace355 --- /dev/null +++ b/components/knx/src/esp32_platform.h @@ -0,0 +1,54 @@ +#ifdef ARDUINO_ARCH_ESP32 +#include "arduino_platform.h" + + + +#include + +class Esp32Platform : public ArduinoPlatform +{ + public: + Esp32Platform(); + Esp32Platform(HardwareSerial* s); + + // uart + void knxUartPins(int8_t rxPin, int8_t txPin); + void setupUart() override; + + // ip stuff + uint32_t currentIpAddress() override; + uint32_t currentSubnetMask() override; + uint32_t currentDefaultGateway() override; + void macAddress(uint8_t* addr) override; + + // unique serial number + uint32_t uniqueSerialNumber() override; + + // basic stuff + void restart(); + + //multicast + void setupMultiCast(uint32_t addr, uint16_t port) override; + void closeMultiCast() override; + bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) override; + + //unicast + bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; + + //memory + uint8_t* getEepromBuffer(uint32_t size); + void commitToEeprom(); + + protected: + IPAddress _remoteIP; + protected: + uint16_t _remotePort; + + private: + WiFiUDP _udp; + int8_t _rxPin = -1; + int8_t _txPin = -1; +}; + +#endif diff --git a/components/knx/src/esp_platform.cpp b/components/knx/src/esp_platform.cpp new file mode 100644 index 0000000..1674e83 --- /dev/null +++ b/components/knx/src/esp_platform.cpp @@ -0,0 +1,135 @@ +#include "esp_platform.h" + +#ifdef ARDUINO_ARCH_ESP8266 +#include +#include +#include + +#include "knx/bits.h" + +#ifndef KNX_SERIAL + #define KNX_SERIAL Serial +#endif + +EspPlatform::EspPlatform() +#ifndef KNX_NO_DEFAULT_UART + : ArduinoPlatform(&KNX_SERIAL) +#endif +{ +} + +EspPlatform::EspPlatform( HardwareSerial* s) : ArduinoPlatform(s) +{ +} + +uint32_t EspPlatform::currentIpAddress() +{ + return WiFi.localIP(); +} + +uint32_t EspPlatform::currentSubnetMask() +{ + return WiFi.subnetMask(); +} + +uint32_t EspPlatform::currentDefaultGateway() +{ + return WiFi.gatewayIP(); +} + +void EspPlatform::macAddress(uint8_t* addr) +{ + wifi_get_macaddr(STATION_IF, addr); +} + +uint32_t EspPlatform::uniqueSerialNumber() +{ + return ESP.getChipId(); +} + +void EspPlatform::restart() +{ + println("restart"); + ESP.reset(); +} + +void EspPlatform::setupMultiCast(uint32_t addr, uint16_t port) +{ + _multicastAddr = htonl(addr); + _multicastPort = port; + IPAddress mcastaddr(_multicastAddr); + + KNX_DEBUG_SERIAL.printf("setup multicast addr: %s port: %d ip: %s\n", mcastaddr.toString().c_str(), port, + WiFi.localIP().toString().c_str()); + uint8 result = _udp.beginMulticast(WiFi.localIP(), mcastaddr, port); + KNX_DEBUG_SERIAL.printf("multicast setup result %d\n", result); +} + +void EspPlatform::closeMultiCast() +{ + _udp.stop(); +} + +bool EspPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) +{ + //printHex("<- ",buffer, len); + _udp.beginPacketMulticast(_multicastAddr, _multicastPort, WiFi.localIP()); + _udp.write(buffer, len); + _udp.endPacket(); + return true; +} + +int EspPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) +{ + int len = _udp.parsePacket(); + + if (len == 0) + return 0; + + if (len > maxLen) + { + KNX_DEBUG_SERIAL.printf("udp buffer to small. was %d, needed %d\n", maxLen, len); + fatalError(); + } + + _udp.read(buffer, len); + //printHex("-> ", buffer, len); + return len; +} + +bool EspPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) +{ + IPAddress ucastaddr(htonl(addr)); + println("sendBytesUniCast endPacket fail"); + + if (_udp.beginPacket(ucastaddr, port) == 1) + { + _udp.write(buffer, len); + + if (_udp.endPacket() == 0) + println("sendBytesUniCast endPacket fail"); + } + else + println("sendBytesUniCast beginPacket fail"); + + return true; +} + +uint8_t* EspPlatform::getEepromBuffer(uint32_t size) +{ + uint8_t* eepromptr = EEPROM.getDataPtr(); + + if (eepromptr == nullptr) + { + EEPROM.begin(size); + eepromptr = EEPROM.getDataPtr(); + } + + return eepromptr; +} + +void EspPlatform::commitToEeprom() +{ + EEPROM.commit(); +} +#endif diff --git a/components/knx/src/esp_platform.h b/components/knx/src/esp_platform.h new file mode 100644 index 0000000..1d4361c --- /dev/null +++ b/components/knx/src/esp_platform.h @@ -0,0 +1,43 @@ +#ifdef ARDUINO_ARCH_ESP8266 +#include "arduino_platform.h" +#include +#include + + +class EspPlatform : public ArduinoPlatform +{ + public: + EspPlatform(); + EspPlatform(HardwareSerial* s); + + // ip stuff + uint32_t currentIpAddress() override; + uint32_t currentSubnetMask() override; + uint32_t currentDefaultGateway() override; + void macAddress(uint8_t* addr) override; + + // unique serial number + uint32_t uniqueSerialNumber() override; + + // basic stuff + void restart(); + + //multicast + void setupMultiCast(uint32_t addr, uint16_t port) override; + void closeMultiCast() override; + bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override; + + //unicast + bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; + + //memory + uint8_t* getEepromBuffer(uint32_t size); + void commitToEeprom(); + private: + WiFiUDP _udp; + uint32_t _multicastAddr; + uint16_t _multicastPort; +}; + +#endif diff --git a/components/knx/src/knx.h b/components/knx/src/knx.h new file mode 100644 index 0000000..6be29da --- /dev/null +++ b/components/knx/src/knx.h @@ -0,0 +1,248 @@ +#pragma once +/** \page Classdiagramm KNX device + * This diagramm shows the most important classes of a normal KNX device. + +@startuml +skinparam monochrome true +skinparam componentStyle uml2 + +package "knx" { +class BusAccessUnit [[class_bus_access_unit.html]] +class DeviceObject [[class_device_object.html]] +class BauSystemBDevice [[class_bau_system_b.html]] +BusAccessUnit<|--BauSystemB +BauSystemB<|--BauSystemBDevice +class ApplicationProgramObject [[class_application_program_object.html]] +BauSystemB*--ApplicationProgramObject +DeviceObject--*BauSystemB +class AddressTableObject [[class_address_table_object.html]] +BauSystemBDevice*--AddressTableObject +class AssociationTableObject [[class_association_table_object.html]] +BauSystemBDevice*--AssociationTableObject +class GroupObjectTableObject [[class_group_object_table_object.html]] +BauSystemBDevice*--GroupObjectTableObject +class GroupObject [[class_group_object.html]] +GroupObject<--GroupObjectTableObject +GroupObjectTableObject<--GroupObject +class ApplicationLayer [[class_application_layer.html]] + +package knx-data-secure +{ +class SecureApplicationLayer [[class_secure_application_layer.html]] +class SecurityInterfaceObject [[class_security_interface_object.html]] +SecureApplicationLayer-->SecurityInterfaceObject +SecurityInterfaceObject-->SecureApplicationLayer +BauSystemBDevice*--"SecurityInterfaceObject" +BauSystemBDevice*--"SecureApplicationLayer" +} +BauSystemBDevice*--"ApplicationLayer" +ApplicationLayer<|--SecureApplicationLayer +SecureApplicationLayer--*BauSystemBDevice +class TransportLayer [[class_transport_layer.html]] +TransportLayer--*BauSystemBDevice +class NetworkLayerDevice [[class_network_layer.html]] +NetworkLayerDevice--*BauSystemBDevice +class NetworkLayerEntity [[class_network_layer_entity.html]] +NetworkLayerEntity--*NetworkLayerDevice +class DataLinkLayer [[class_data_link_layer.html]] +ApplicationLayer-->SecureApplicationLayer +SecureApplicationLayer-->ApplicationLayer +ApplicationLayer-->BusAccessUnit +ApplicationLayer-->TransportLayer +SecureApplicationLayer-->TransportLayer +TransportLayer-->ApplicationLayer +TransportLayer-->SecureApplicationLayer +TransportLayer-->NetworkLayerDevice +NetworkLayerDevice-->TransportLayer +NetworkLayerEntity-->DataLinkLayer +DataLinkLayer-->NetworkLayerEntity +TransportLayer-->AddressTableObject +SecureApplicationLayer-->AddressTableObject +SecureApplicationLayer-->DeviceObject +DataLinkLayer-->DeviceObject +ApplicationLayer-->AssociationTableObject +class Dpt [[class_dpt.html]] +GroupObject->Dpt + +package knx-ip +{ +class IpDataLinkLayer [[class_ip_data_link_layer.html]] +IpDataLinkLayer--|>DataLinkLayer +class Bau57B0 [[class_bau57_b0.html]] +Bau57B0--|>BauSystemBDevice +Bau57B0*--IpDataLinkLayer +class IpParameterObject [[class_ip_parameter_object.html]] +IpParameterObject-->DeviceObject +Bau57B0*--IpParameterObject +IpDataLinkLayer-->IpParameterObject +} +package knx-tp +{ +class TpUartDataLinkLayer [[class_tp_uart_data_link_layer.html]] +TpUartDataLinkLayer--|>DataLinkLayer +class Bau07B0 [[class_bau07_b0.html]] +Bau07B0*--TpUartDataLinkLayer +Bau07B0--|>BauSystemBDevice +} +package knx-rf +{ +class RfDataLinkLayer [[class_rf_data_link_layer.html]] +RfDataLinkLayer--|>DataLinkLayer +class Bau27B0 [[class_bau27_b0.html]] +Bau27B0*--"RfDataLinkLayer" +Bau27B0--|>BauSystemBDevice +class RfMediumObject [[class_rf_medium_object.html]] +Bau27B0*--"RfMediumObject" +class RfPhysicalLayer [[class_rf_physical_layer.html]] +"RfDataLinkLayer"*--"RfPhysicalLayer" +} +package knx-cemi-server +{ +class CemiServer [[class_cemi_server.html]] +class CemiServerObject [[class_cemi_server_object.html]] +class UsbTunnelInterface [[class_usb_tunnel_inerface.html]] +CemiServer*--"UsbTunnelInterface" +Bau57B0*--"CemiServer" +Bau57B0*--"CemiServerObject" +Bau27B0*--"CemiServer" +Bau27B0*--"CemiServerObject" +Bau07B0*--"CemiServer" +Bau07B0*--"CemiServerObject" +} +CemiServer-->DataLinkLayer +DataLinkLayer-->CemiServer +} + +package platform +{ +class Platform [[class_platform.html]] +class ArduinoPlatform [[class_arduino_platform.html]] +class SamdPlatform [[class_samd_platform.html]] +Platform<|--ArduinoPlatform +ArduinoPlatform<|--SamdPlatform +class EspPlatform [[class_esp_platform.html]] +ArduinoPlatform<|--EspPlatform +class Esp32Platform [[class_esp32_platform.html]] +ArduinoPlatform<|--Esp32Platform +class Stm32Platform [[class_stm32_platform.html]] +ArduinoPlatform<|--Stm32Platform +class LinuxPlatform [[class_linux_platform.html]] +LinuxPlatform--|>Platform +class CC1310Platform [[class_cc1310_platform.html]] +CC1310Platform--|>Platform +class RP2040ArduinoPlatform [[class_rp2040_arduino_platform.html]] +RP2040ArduinoPlatform--|>ArduinoPlatform +} + +package frontend +{ +class KnxFacade [[class_knx_facade.html]] +BauSystemBDevice<--KnxFacade +} +knx-->Platform +@enduml + +* \page Classdiagramm KNX coupler + * This diagramm shows the most important classes of a KNX coupler. +@startuml + +skinparam monochrome true +skinparam componentStyle uml2 + +package "knx" { +class BusAccessUnit [[class_bus_access_unit.html]] +class DeviceObject [[class_device_object.html]] +class BauSystemBCoupler [[class_bau_system_b.html]] +BusAccessUnit<|--BauSystemB +BauSystemB<|--BauSystemBCoupler +class ApplicationProgramObject [[class_application_program_object.html]] +BauSystemB*--ApplicationProgramObject +DeviceObject--*BauSystemB +class ApplicationLayer [[class_application_layer.html]] + +package knx-data-secure +{ +class SecureApplicationLayer [[class_secure_application_layer.html]] +class SecurityInterfaceObject [[class_security_interface_object.html]] +SecureApplicationLayer-->SecurityInterfaceObject +SecurityInterfaceObject-->SecureApplicationLayer +BauSystemBCoupler*--"SecurityInterfaceObject" +BauSystemBCoupler*--"SecureApplicationLayer" +} +BauSystemBCoupler*--"ApplicationLayer" +ApplicationLayer<|--SecureApplicationLayer +SecureApplicationLayer--*BauSystemBCoupler +class TransportLayer [[class_transport_layer.html]] +TransportLayer--*BauSystemBCoupler +class NetworkLayerEntity [[class_network_layer_entity.html]] +class NetworkLayerCoupler [[class_network_layer.html]] +{ +NetworkLayerEntity[] _networkLayerEntities +} +NetworkLayerCoupler*--"NetworkLayerEntity" +NetworkLayerCoupler--*BauSystemBCoupler +class DataLinkLayer [[class_data_link_layer.html]] +ApplicationLayer-->SecureApplicationLayer +SecureApplicationLayer-->ApplicationLayer +ApplicationLayer-->BusAccessUnit +ApplicationLayer-->TransportLayer +SecureApplicationLayer-->TransportLayer +TransportLayer-->ApplicationLayer +TransportLayer-->SecureApplicationLayer +TransportLayer-->NetworkLayerCoupler +NetworkLayerCoupler-->TransportLayer +NetworkLayerEntity-->DataLinkLayer +DataLinkLayer-->NetworkLayerEntity +SecureApplicationLayer-->DeviceObject +DataLinkLayer-->DeviceObject + +package knx-ip-tp +{ +class IpDataLinkLayer [[class_ip_data_link_layer.html]] +IpDataLinkLayer--|>DataLinkLayer +class TpUartDataLinkLayer [[class_tp_uart_data_link_layer.html]] +TpUartDataLinkLayer--|>DataLinkLayer +class Bau091A [[class_bau09_1a.html]] +Bau091A--|>BauSystemBCoupler +Bau091A*--IpDataLinkLayer +Bau091A*--TpUartDataLinkLayer +class RouterObject [[class_router_object.html]] +class IpParameterObject [[class_ip_parameter_object.html]] +IpParameterObject-->DeviceObject +Bau091A*--"RouterObject" +Bau091A*--IpParameterObject +IpDataLinkLayer-->IpParameterObject +} +} + +package platform +{ +class Platform [[class_platform.html]] +class ArduinoPlatform [[class_arduino_platform.html]] +class SamdPlatform [[class_samd_platform.html]] +Platform<|--ArduinoPlatform +ArduinoPlatform<|--SamdPlatform +class EspPlatform [[class_esp_platform.html]] +ArduinoPlatform<|--EspPlatform +class Esp32Platform [[class_esp32_platform.html]] +ArduinoPlatform<|--Esp32Platform +class Stm32Platform [[class_stm32_platform.html]] +ArduinoPlatform<|--Stm32Platform +class LinuxPlatform [[class_linux_platform.html]] +LinuxPlatform--|>Platform +class CC1310Platform [[class_cc1310_platform.html]] +CC1310Platform--|>Platform +class RP2040ArduinoPlatform [[class_rp2040_arduino_platform.html]] +RP2040ArduinoPlatform--|>ArduinoPlatform +} + +package frontend +{ +class KnxFacade [[class_knx_facade.html]] +BauSystemBCoupler<--KnxFacade +} +knx-->Platform +@enduml + + */ +#include "knx_facade.h" diff --git a/components/knx/src/knx/address_table_object.cpp b/components/knx/src/knx/address_table_object.cpp new file mode 100644 index 0000000..23ab592 --- /dev/null +++ b/components/knx/src/knx/address_table_object.cpp @@ -0,0 +1,96 @@ +#include + +#include "address_table_object.h" +#include "bits.h" +#include "data_property.h" + +using namespace std; + +AddressTableObject::AddressTableObject(Memory& memory) + : TableObject(memory) +{ + Property* properties[] = + { + new DataProperty(PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_ADDR_TABLE) + }; + + TableObject::initializeProperties(sizeof(properties), properties); +} + +uint16_t AddressTableObject::entryCount() +{ + // after programming without GA the module hangs + if (loadState() != LS_LOADED || _groupAddresses[0] == 0xFFFF) + return 0; + + return ntohs(_groupAddresses[0]); +} + +uint16_t AddressTableObject::getGroupAddress(uint16_t tsap) +{ + if (loadState() != LS_LOADED || tsap > entryCount() ) + return 0; + + return ntohs(_groupAddresses[tsap]); +} + +uint16_t AddressTableObject::getTsap(uint16_t addr) +{ + uint16_t size = entryCount(); +#ifdef USE_BINSEARCH + + uint16_t low, high, i; + low = 1; + high = size; + + while (low <= high) + { + i = (low + high) / 2; + uint16_t ga = ntohs(_groupAddresses[i]); + + if (ga == addr) + return i; + + if (addr < ga) + high = i - 1; + else + low = i + 1 ; + } + +#else + + for (uint16_t i = 1; i <= size; i++) + if (ntohs(_groupAddresses[i]) == addr) + return i; + +#endif + return 0; +} + +#pragma region SaveRestore + +const uint8_t* AddressTableObject::restore(const uint8_t* buffer) +{ + buffer = TableObject::restore(buffer); + + _groupAddresses = (uint16_t*)data(); + + return buffer; +} + +#pragma endregion + +bool AddressTableObject::contains(uint16_t addr) +{ + return (getTsap(addr) > 0); +} + +void AddressTableObject::beforeStateChange(LoadState& newState) +{ + TableObject::beforeStateChange(newState); + + if (newState != LS_LOADED) + return; + + _groupAddresses = (uint16_t*)data(); +} diff --git a/components/knx/src/knx/address_table_object.h b/components/knx/src/knx/address_table_object.h new file mode 100644 index 0000000..0fd2890 --- /dev/null +++ b/components/knx/src/knx/address_table_object.h @@ -0,0 +1,57 @@ +#pragma once + +#include "table_object.h" +/** + * This class represents the group address table. It provides a mapping between transport layer + * service access points (TSAP) and group addresses. The TSAP can be imagined as an index to the array + * of group addresses. + * + * See section 4.10 of @cite knx:3/5/1 for further details. + * It implements realisation type 7 (see section 4.10.7 of @cite knx:3/5/1). + */ +class AddressTableObject : public TableObject +{ + public: + /** + * The constructor. + * + * @param memory This parameter is only passed to the constructor of TableObject and is not used by this class. + */ + AddressTableObject(Memory& memory); + const uint8_t* restore(const uint8_t* buffer) override; + + /** + * returns the number of group addresses of the object. + */ + uint16_t entryCount(); + /** + * Get the group address mapped to a TSAP. + * + * @param tsap The TSAP of which to get the group address for. + * + * @return the groupAddress if found or zero if no group address was found. + */ + uint16_t getGroupAddress(uint16_t tsap); + /** + * Get the TSAP mapped to a group address. + * + * @param groupAddress the group address of which to get the TSAP for. + * + * @return the TSAP if found or zero if no tsap was found. + */ + uint16_t getTsap(uint16_t groupAddress); + /** + * Check if the address table contains a group address. + * + * @param groupAddress the group address to check + * + * @return true if the address table contains the group address, false otherwise + */ + bool contains(uint16_t groupAddress); + + protected: + void beforeStateChange(LoadState& newState) override; + + private: + uint16_t* _groupAddresses = 0; +}; diff --git a/components/knx/src/knx/aes.c b/components/knx/src/knx/aes.c new file mode 100644 index 0000000..07d4ba9 --- /dev/null +++ b/components/knx/src/knx/aes.c @@ -0,0 +1,606 @@ +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include // CBC mode, for memset +#include "aes.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 + +#if defined(AES256) && (AES256 == 1) + #define Nk 8 + #define Nr 14 +#elif defined(AES192) && (AES192 == 1) + #define Nk 6 + #define Nr 12 +#else + #define Nk 4 // The number of 32 bit words in a key. + #define Nr 10 // The number of rounds in AES Cipher. +#endif + +// jcallan@github points out that declaring Multiply as a function +// reduces code size considerably with the Keil ARM compiler. +// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 +#ifndef MULTIPLY_AS_A_FUNCTION + #define MULTIPLY_AS_A_FUNCTION 0 +#endif + + + + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + + + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = +{ + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 +}; + +static const uint8_t rsbox[256] = +{ + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d +}; + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = +{ + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 +}; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +/* +static uint8_t getSBoxValue(uint8_t num) +{ + return sbox[num]; +} +*/ +#define getSBoxValue(num) (sbox[(num)]) +/* +static uint8_t getSBoxInvert(uint8_t num) +{ + return rsbox[num]; +} +*/ +#define getSBoxInvert(num) (rsbox[(num)]) + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0] = RoundKey[k + 0]; + tempa[1] = RoundKey[k + 1]; + tempa[2] = RoundKey[k + 2]; + tempa[3] = RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i / Nk]; + } + +#if defined(AES256) && (AES256 == 1) + + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } + +#endif + j = i * 4; + k = (i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) +{ + KeyExpansion(ctx->RoundKey, key); +} +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +#endif + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) +{ + uint8_t i, j; + + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t* state) +{ + uint8_t i, j; + + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; + Tm = xtime(Tm); + (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; + Tm = xtime(Tm); + (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; + Tm = xtime(Tm); + (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; + Tm = xtime(Tm); + (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary +// The compiler seems to be able to vectorize the operation better this way. +// See https://github.com/kokke/tiny-AES-c/pull/34 +#if MULTIPLY_AS_A_FUNCTION +static uint8_t Multiply(uint8_t x, uint8_t y) +{ + return (((y & 1) * x) ^ + ((y >> 1 & 1) * xtime(x)) ^ + ((y >> 2 & 1) * xtime(xtime(x))) ^ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ +} +#else +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +#endif + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns(state_t* state) +{ + int i; + uint8_t a, b, c, d; + + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes(state_t* state) +{ + uint8_t i, j; + + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to right + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +// Cipher is the main function that encrypts the PlainText. +static void Cipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + + if (round == Nr) + { + break; + } + + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static void InvCipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (round = (Nr - 1); ; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + + if (round == 0) + { + break; + } + + InvMixColumns(state); + } + +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ +#if defined(ECB) && (ECB == 1) + + +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher((state_t*)buf, ctx->RoundKey); +} + +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher((state_t*)buf, ctx->RoundKey); +} + + +#endif // #if defined(ECB) && (ECB == 1) + + + + + +#if defined(CBC) && (CBC == 1) + + +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) +{ + uint8_t i; + + for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, uint32_t length) +{ + uintptr_t i; + uint8_t* Iv = ctx->Iv; + + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, uint32_t length) +{ + uintptr_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} + +#endif // #if defined(CBC) && (CBC == 1) + + + +#if defined(CTR) && (CTR == 1) + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, uint32_t length) +{ + uint8_t buffer[AES_BLOCKLEN]; + + unsigned i; + int bi; + + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)buffer, ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + + ctx->Iv[bi] += 1; + break; + } + + bi = 0; + } + + buf[i] = (buf[i] ^ buffer[bi]); + } +} + +#endif // #if defined(CTR) && (CTR == 1) + diff --git a/components/knx/src/knx/aes.h b/components/knx/src/knx/aes.h new file mode 100644 index 0000000..c2a2159 --- /dev/null +++ b/components/knx/src/knx/aes.h @@ -0,0 +1,90 @@ +#ifndef _AES_H_ +#define _AES_H_ + +#include + +// #define the macros below to 1/0 to enable/disable the mode of operation. +// +// CBC enables AES encryption in CBC-mode of operation. +// CTR enables encryption in counter-mode. +// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. + +// The #ifndef-guard allows it to be configured before #include'ing or at compile time. +#ifndef CBC + #define CBC 1 +#endif + +#ifndef ECB + #define ECB 1 +#endif + +#ifndef CTR + #define CTR 1 +#endif + + +#define AES128 1 +//#define AES192 1 +//#define AES256 1 + +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only + +#if defined(AES256) && (AES256 == 1) + #define AES_KEYLEN 32 + #define AES_keyExpSize 240 +#elif defined(AES192) && (AES192 == 1) + #define AES_KEYLEN 24 + #define AES_keyExpSize 208 +#else + #define AES_KEYLEN 16 // Key length in bytes + #define AES_keyExpSize 176 +#endif + +struct AES_ctx +{ + uint8_t RoundKey[AES_keyExpSize]; +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) + uint8_t Iv[AES_BLOCKLEN]; +#endif +}; + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) + void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); + void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); +#endif + +#if defined(ECB) && (ECB == 1) + // buffer size is exactly AES_BLOCKLEN bytes; + // you need only AES_init_ctx as IV is not used in ECB + // NB: ECB is considered insecure for most uses + void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); + void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); + +#endif // #if defined(ECB) && (ECB == !) + + +#if defined(CBC) && (CBC == 1) + // buffer size MUST be mutile of AES_BLOCKLEN; + // Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme + // NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() + // no IV should ever be reused with the same key + void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, uint32_t length); + void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, uint32_t length); + +#endif // #if defined(CBC) && (CBC == 1) + + +#if defined(CTR) && (CTR == 1) + + // Same function for encrypting as for decrypting. + // IV is incremented for every block, and used after encryption as XOR-compliment for output + // Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme + // NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() + // no IV should ever be reused with the same key + void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, uint32_t length); + +#endif // #if defined(CTR) && (CTR == 1) + + +#endif // _AES_H_ diff --git a/components/knx/src/knx/aes.hpp b/components/knx/src/knx/aes.hpp new file mode 100644 index 0000000..4b234b7 --- /dev/null +++ b/components/knx/src/knx/aes.hpp @@ -0,0 +1,12 @@ +#ifndef _AES_HPP_ +#define _AES_HPP_ + +#ifndef __cplusplus + #error Do not include the hpp header in a c project! +#endif //__cplusplus + +extern "C" { +#include "aes.h" +} + +#endif //_AES_HPP_ diff --git a/components/knx/src/knx/apdu.cpp b/components/knx/src/knx/apdu.cpp new file mode 100644 index 0000000..966de35 --- /dev/null +++ b/components/knx/src/knx/apdu.cpp @@ -0,0 +1,59 @@ +#include "apdu.h" +#include "cemi_frame.h" +#include "bits.h" + +APDU::APDU(uint8_t* data, CemiFrame& frame): _data(data), _frame(frame) +{ +} + +ApduType APDU::type() +{ + uint16_t apci; + apci = getWord(_data); + popWord(apci, _data); + apci &= 0x3ff; + + if ((apci >> 6) < 11 && (apci >> 6) != 7) //short apci + apci &= 0x3c0; + + return (ApduType)apci; +} + +void APDU::type(ApduType atype) +{ + // ApduType is in big endian so convert to host first, pushWord converts back + pushWord((uint16_t)atype, _data); +} + +uint8_t* APDU::data() +{ + return _data + 1; +} + +CemiFrame& APDU::frame() +{ + return _frame; +} + +uint8_t APDU::length() const +{ + return _frame.npdu().octetCount(); +} + +void APDU::printPDU() +{ + print("APDU: "); + print(type(), HEX); + print(" "); + print(_data[0] & 0x3, HEX); + + for (uint8_t i = 1; i < length() + 1; ++i) + { + if (i) + print(" "); + + print(_data[i], HEX); + } + + println(); +} diff --git a/components/knx/src/knx/apdu.h b/components/knx/src/knx/apdu.h new file mode 100644 index 0000000..7eacd32 --- /dev/null +++ b/components/knx/src/knx/apdu.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include "knx_types.h" + +class CemiFrame; + +/** + * This class represents an Application Protocol Data Unit. It is part of a CemiFrame. + */ +class APDU +{ + friend class CemiFrame; + + public: + /** + * Get the type of the APDU. + */ + ApduType type(); + /** + * Set the type of the APDU. + */ + void type(ApduType atype); + /** + * Get a pointer to the data. + */ + uint8_t* data(); + /** + * Get the CemiFrame this APDU is part of. + */ + CemiFrame& frame(); + /** + * Get the length of the APDU. (This is actually the octet count of the NPDU.) + */ + uint8_t length() const; + /** + * Print the contents of the APDU to console. + */ + void printPDU(); + + protected: + /** + * The constructor. + * @param data The data of the APDU. Encoding depends on the ::ApduType. The class doesn't + * take possession of this pointer. + * @param frame The CemiFrame this APDU is part of. + */ + APDU(uint8_t* data, CemiFrame& frame); + + private: + uint8_t* _data = 0; + CemiFrame& _frame; +}; \ No newline at end of file diff --git a/components/knx/src/knx/application_layer.cpp b/components/knx/src/knx/application_layer.cpp new file mode 100644 index 0000000..4688ada --- /dev/null +++ b/components/knx/src/knx/application_layer.cpp @@ -0,0 +1,1546 @@ +#include "application_layer.h" +#include "transport_layer.h" +#include "cemi_frame.h" +#include "association_table_object.h" +#include "apdu.h" +#include "bau.h" +#include "string.h" +#include "bits.h" +#include + +const SecurityControl ApplicationLayer::noSecurity {.toolAccess = false, .dataSecurity = DataSecurity::None}; + +ApplicationLayer::ApplicationLayer(BusAccessUnit& bau) : _bau(bau) +{ +} + +void ApplicationLayer::transportLayer(TransportLayer& layer) +{ + _transportLayer = &layer; +} + +void ApplicationLayer::associationTableObject(AssociationTableObject& assocTable) +{ + _assocTable = &assocTable; +} + +#pragma region TL Callbacks + +void ApplicationLayer::dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu) +{ + dataGroupIndication(hopType, priority, tsap, apdu, noSecurity); +} + +void ApplicationLayer::dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl) +{ + if (_assocTable == nullptr) + return; + + uint8_t len = apdu.length(); + uint8_t dataArray[len]; + uint8_t* data = dataArray; + memcpy(data, apdu.data(), len); + + if (len == 1) + { + //less than six bit are encoded in first byte + *data &= 0x3f; + } + else + { + data += 1; + len -= 1; + } + + uint16_t startIdx = 0; + int32_t asap = _assocTable->nextAsap(tsap, startIdx); + + for (; asap != -1; asap = _assocTable->nextAsap(tsap, startIdx)) + { + switch (apdu.type()) + { + case GroupValueRead: + _bau.groupValueReadIndication(asap, priority, hopType, secCtrl); + break; + + case GroupValueResponse: + _bau.groupValueReadAppLayerConfirm(asap, priority, hopType, secCtrl, data, len); + break; + + case GroupValueWrite: + _bau.groupValueWriteIndication(asap, priority, hopType, secCtrl, data, len); + + default: + /* other apdutypes are not valid here. If they appear do nothing */ + break; + } + } +} + +void ApplicationLayer::dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status) +{ + dataGroupConfirm(ack, hopType, priority, tsap, apdu, noSecurity, status); +} + +void ApplicationLayer::dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl, bool status) +{ + switch (apdu.type()) + { + case GroupValueRead: + if (_savedAsapReadRequest > 0) + _bau.groupValueReadLocalConfirm(ack, _savedAsapReadRequest, priority, hopType, secCtrl, status); + + _savedAsapReadRequest = 0; + break; + + case GroupValueResponse: + if (_savedAsapResponse > 0) + _bau.groupValueReadResponseConfirm(ack, _savedAsapResponse, priority, hopType, secCtrl, apdu.data(), apdu.length() - 1, status); + + _savedAsapResponse = 0; + break; + + case GroupValueWrite: + if (_savedAsapWriteRequest > 0) + _bau.groupValueWriteLocalConfirm(ack, _savedAsapWriteRequest, priority, hopType, secCtrl, apdu.data(), apdu.length() - 1, status); + + _savedAsapWriteRequest = 0; + break; + + default: + print("datagroup-confirm: unhandled APDU-Type: "); + println(apdu.type()); + } +} + +void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) +{ + dataBroadcastIndication(hopType, priority, source, apdu, noSecurity); +} + +void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, const SecurityControl& secCtrl) +{ + uint8_t* data = apdu.data(); + + switch (apdu.type()) + { + case IndividualAddressWrite: + { + uint16_t newAddress; + popWord(newAddress, data + 1); + _bau.individualAddressWriteIndication(hopType, secCtrl, newAddress); + break; + } + + case IndividualAddressRead: + _bau.individualAddressReadIndication(hopType, secCtrl); + break; + + case IndividualAddressResponse: + _bau.individualAddressReadAppLayerConfirm(hopType, secCtrl, apdu.frame().sourceAddress()); + break; + + case IndividualAddressSerialNumberRead: + { + uint8_t* knxSerialNumber = &data[1]; + _bau.individualAddressSerialNumberReadIndication(priority, hopType, secCtrl, knxSerialNumber); + break; + } + + case IndividualAddressSerialNumberResponse: + { + uint16_t domainAddress; + popWord(domainAddress, data + 7); + _bau.individualAddressSerialNumberReadAppLayerConfirm(hopType, secCtrl, data + 1, apdu.frame().sourceAddress(), + domainAddress); + break; + } + + case IndividualAddressSerialNumberWrite: + { + uint8_t* knxSerialNumber = &data[1]; + uint16_t newIndividualAddress; + popWord(newIndividualAddress, &data[7]); + _bau.individualAddressSerialNumberWriteIndication(priority, hopType, secCtrl, newIndividualAddress, knxSerialNumber); + break; + } + + default: + print("Broadcast-indication: unhandled APDU-Type: "); + println(apdu.type()); + break; + } +} + +void ApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, bool status) +{ + dataBroadcastConfirm(ack, hopType, priority, apdu, noSecurity, status); +} + +void ApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl, bool status) +{ + uint8_t* data = apdu.data(); + + switch (apdu.type()) + { + case IndividualAddressWrite: + { + uint16_t newAddress; + popWord(newAddress, data + 1); + _bau.individualAddressWriteLocalConfirm(ack, hopType, secCtrl, newAddress, status); + break; + } + + case IndividualAddressRead: + _bau.individualAddressReadLocalConfirm(ack, hopType, secCtrl, status); + break; + + case IndividualAddressResponse: + _bau.individualAddressReadResponseConfirm(ack, hopType, secCtrl, status); + break; + + case IndividualAddressSerialNumberRead: + _bau.individualAddressSerialNumberReadLocalConfirm(ack, hopType, secCtrl, data + 1, status); + break; + + case IndividualAddressSerialNumberResponse: + { + uint16_t domainAddress; + popWord(domainAddress, data + 7); + _bau.individualAddressSerialNumberReadResponseConfirm(ack, hopType, secCtrl, data + 1, domainAddress, status); + break; + } + + case IndividualAddressSerialNumberWrite: + { + uint16_t newAddress; + popWord(newAddress, data + 7); + _bau.individualAddressSerialNumberWriteLocalConfirm(ack, hopType, secCtrl, data + 1, newAddress, status); + break; + } + + default: + print("Broadcast-confirm: unhandled APDU-Type: "); + println(apdu.type()); + break; + } +} + +void ApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) +{ + dataSystemBroadcastIndication(hopType, priority, source, apdu, noSecurity); +} + +void ApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, const SecurityControl& secCtrl) +{ + const uint8_t* data = apdu.data(); + + switch (apdu.type()) + { + // TODO: testInfo could be of any length + case SystemNetworkParameterRead: + { + uint16_t objectType; + uint16_t propertyId; + uint8_t testInfo[2]; + popWord(objectType, data + 1); + popWord(propertyId, data + 3); + popByte(testInfo[0], data + 4); + popByte(testInfo[1], data + 5); + propertyId = (propertyId >> 4) & 0x0FFF; + testInfo[0] &= 0x0F; + _bau.systemNetworkParameterReadIndication(priority, hopType, secCtrl, objectType, propertyId, testInfo, sizeof(testInfo)); + break; + } + + case DomainAddressSerialNumberWrite: + { + const uint8_t* knxSerialNumber = &data[1]; + const uint8_t* domainAddress = &data[7]; + _bau.domainAddressSerialNumberWriteIndication(priority, hopType, secCtrl, domainAddress, knxSerialNumber); + break; + } + + case DomainAddressSerialNumberRead: + { + const uint8_t* knxSerialNumber = &data[1]; + _bau.domainAddressSerialNumberReadIndication(priority, hopType, secCtrl, knxSerialNumber); + break; + } + + default: + print("SystemBroadcast-indication: unhandled APDU-Type: "); + println(apdu.type()); + break; + } +} + +void ApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status) +{ + dataSystemBroadcastConfirm(hopType, priority, apdu, noSecurity, status); +} + +void ApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl, bool status) +{ + const uint8_t* data = apdu.data(); + + switch (apdu.type()) + { + // TODO: testInfo could be of any length + case SystemNetworkParameterRead: + { + uint16_t objectType; + uint16_t propertyId; + uint8_t testInfo[2]; + popWord(objectType, data + 1); + popWord(propertyId, data + 3); + popByte(testInfo[0], data + 4); + popByte(testInfo[1], data + 5); + propertyId = (propertyId >> 4) & 0x0FFF; + testInfo[0] &= 0x0F; + _bau.systemNetworkParameterReadLocalConfirm(priority, hopType, secCtrl, objectType, propertyId, testInfo, sizeof(testInfo), status); + break; + } + + case DomainAddressSerialNumberWrite: + { + const uint8_t* knxSerialNumber = &data[1]; + const uint8_t* domainAddress = &data[7]; + _bau.domainAddressSerialNumberWriteLocalConfirm(priority, hopType, secCtrl, domainAddress, knxSerialNumber, status); + break; + } + + case DomainAddressSerialNumberRead: + { + const uint8_t* knxSerialNumber = &data[1]; + _bau.domainAddressSerialNumberReadLocalConfirm(priority, hopType, secCtrl, knxSerialNumber, status); + break; + } + + default: + print("SystemBroadcast-confirm: unhandled APDU-Type: "); + println(apdu.type()); + break; + } +} + +void ApplicationLayer::dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) +{ + dataIndividualIndication(hopType, priority, source, apdu, noSecurity); +} + +void ApplicationLayer::dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl) +{ + individualIndication(hopType, priority, tsap, apdu, secCtrl); +} + +void ApplicationLayer::dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status) +{ + dataIndividualConfirm(ack, hopType, priority, tsap, apdu, noSecurity, status); +} + +void ApplicationLayer::dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl, bool status) +{ + individualConfirm(ack, hopType, priority, tsap, apdu, secCtrl, status); +} + +void ApplicationLayer::connectIndication(uint16_t tsap) +{ + _connectedTsap = tsap; +} + +void ApplicationLayer::connectConfirm(uint16_t destination, uint16_t tsap, bool status) +{ + if (status) + { + _connectedTsap = tsap; + _bau.connectConfirm(tsap); + } + else + _connectedTsap = -1; +} + +void ApplicationLayer::disconnectIndication(uint16_t tsap) +{ + _connectedTsap = -1; +} + +void ApplicationLayer::disconnectConfirm(Priority priority, uint16_t tsap, bool status) +{ + _connectedTsap = -1; +} + +void ApplicationLayer::dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu) +{ + dataConnectedIndication(priority, tsap, apdu, noSecurity); +} + +void ApplicationLayer::dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl) +{ + individualIndication(NetworkLayerParameter, priority, tsap, apdu, secCtrl); +} + +void ApplicationLayer::dataConnectedConfirm(uint16_t tsap) +{ + dataConnectedConfirm(tsap, noSecurity); +} + +void ApplicationLayer::dataConnectedConfirm(uint16_t tsap, const SecurityControl& secCtrl) +{ + //FIXME: implement dataConnectedConfirm DataSecurity +} +#pragma endregion +void ApplicationLayer::groupValueReadRequest(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl) +{ + if (_assocTable == nullptr) + return; + + _savedAsapReadRequest = asap; + CemiFrame frame(1); + APDU& apdu = frame.apdu(); + apdu.type(GroupValueRead); + + int32_t value = _assocTable->translateAsap(asap); + + if (value < 0) + return; // there is no tsap in association table for this asap + + uint16_t tsap = (uint16_t)value; + + // first to bus then to itself + dataGroupRequest(ack, hopType, priority, tsap, apdu, secCtrl); + dataGroupIndication(hopType, priority, tsap, apdu, secCtrl); +} + +void ApplicationLayer::groupValueReadResponse(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength) +{ + _savedAsapResponse = asap; + groupValueSend(GroupValueResponse, ack, asap, priority, hopType, secCtrl, data, dataLength); +} + +void ApplicationLayer::groupValueWriteRequest(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength) +{ + _savedAsapWriteRequest = asap; + groupValueSend(GroupValueWrite, ack, asap, priority, hopType, secCtrl, data, dataLength); +} + +void ApplicationLayer::individualAddressWriteRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint16_t newaddress) +{ + CemiFrame frame(3); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressWrite); + uint8_t* apduData = apdu.data(); + pushWord(newaddress, apduData + 1); + dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void ApplicationLayer::individualAddressReadRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl) +{ + CemiFrame frame(1); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressRead); + dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void ApplicationLayer::individualAddressReadResponse(AckType ack, HopCountType hopType, const SecurityControl& secCtrl) +{ + CemiFrame frame(1); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressResponse); + dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void ApplicationLayer::individualAddressSerialNumberReadRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber) +{ + CemiFrame frame(7); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressSerialNumberRead); + uint8_t* data = apdu.data() + 1; + memcpy(data, serialNumber, 6); + dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void ApplicationLayer::individualAddressSerialNumberReadResponse(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* serialNumber, uint16_t domainAddress) +{ + CemiFrame frame(7); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressSerialNumberResponse); + uint8_t* data = apdu.data() + 1; + memcpy(data, serialNumber, 6); + data += 6; + pushWord(domainAddress, data); + dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void ApplicationLayer::individualAddressSerialNumberWriteRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, + uint16_t newaddress) +{ + CemiFrame frame(13); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressSerialNumberWrite); + uint8_t* data = apdu.data() + 1; + memcpy(data, serialNumber, 6); + data += 6; + pushWord(newaddress, data); + dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void ApplicationLayer::deviceDescriptorReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t descriptorType) +{ + CemiFrame frame(1); + APDU& apdu = frame.apdu(); + apdu.type(DeviceDescriptorRead); + uint8_t* data = apdu.data(); + *data |= (descriptorType & 0x3f); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::deviceDescriptorReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t descriptorType, uint8_t* deviceDescriptor) +{ + uint8_t length = 0; + + switch (descriptorType) + { + case 0: + length = 3; + break; + + case 2: + length = 14; + break; + + default: + length = 1; + descriptorType = 0x3f; + break; + } + + CemiFrame frame(length); + APDU& apdu = frame.apdu(); + apdu.type(DeviceDescriptorResponse); + uint8_t* data = apdu.data(); + *data |= (descriptorType & 0x3f); + + if (length > 1) + memcpy(data + 1, deviceDescriptor, length - 1); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::connectRequest(uint16_t destination, Priority priority) +{ + _transportLayer->connectRequest(destination, priority); +} + +void ApplicationLayer::disconnectRequest(Priority priority) +{ + _transportLayer->disconnectRequest(_connectedTsap, priority); +} + +void ApplicationLayer::restartRequest(AckType ack, Priority priority, HopCountType hopType, const SecurityControl& secCtrl) +{ + CemiFrame frame(1); + APDU& apdu = frame.apdu(); + apdu.type(Restart); + + individualSend(ack, hopType, priority, _connectedTsap, apdu, secCtrl); +} + +void ApplicationLayer::restartResponse(AckType ack, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t errorCode, uint16_t processTime) +{ + CemiFrame frame(4); + APDU& apdu = frame.apdu(); + apdu.type(Restart); + uint8_t* data = apdu.data(); + data[0] |= (1 << 5) | 1; // Set response bit and a restart type of "master reset". Only the master reset sends a response. + data[1] = errorCode; + data[2] = processTime >> 8; + data[3] = processTime & 0xFF; + + individualSend(ack, hopType, priority, _connectedTsap, apdu, secCtrl); +} + +//TODO: ApplicationLayer::systemNetworkParameterReadRequest() +void ApplicationLayer::systemNetworkParameterReadResponse(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t propertyId, + uint8_t* testInfo, uint16_t testInfoLength, + uint8_t* testResult, uint16_t testResultLength) +{ + CemiFrame frame(testInfoLength + testResultLength + 3 + 1); // PID and testInfo share an octet (+3) and +1 for APCI byte(?) + APDU& apdu = frame.apdu(); + apdu.type(SystemNetworkParameterResponse); + uint8_t* data = apdu.data() + 1; + + pushWord(objectType, data); + pushWord((propertyId << 4) & 0xFFF0, data + 2); // Reserved bits for test_info are always 0 + uint8_t* pData = pushByteArray(&testInfo[1], testInfoLength - 1, data + 4); // TODO: upper reserved bits (testInfo + 0) have to put into the lower bits of data + 3 + memcpy(pData, testResult, testResultLength); + + //apdu.printPDU(); + + dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu, secCtrl); +} + +//TODO: ApplicationLayer::domainAddressSerialNumberWriteRequest() +//TODO: ApplicationLayer::domainAddressSerialNumberReadRequest() +void ApplicationLayer::domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber) +{ + CemiFrame frame(13); + APDU& apdu = frame.apdu(); + apdu.type(DomainAddressSerialNumberResponse); + + uint8_t* data = apdu.data() + 1; + + memcpy(data, knxSerialNumber, 6); + memcpy(data + 6, rfDoA, 6); + + //apdu.printPDU(); + + dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu, secCtrl); +} + +//TODO: ApplicationLayer::IndividualAddressSerialNumberWriteRequest() +//TODO: ApplicationLayer::IndividualAddressSerialNumberReadRequest() +void ApplicationLayer::IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* domainAddress, + const uint8_t* knxSerialNumber) +{ + CemiFrame frame(11); + APDU& apdu = frame.apdu(); + apdu.type(IndividualAddressSerialNumberResponse); + + uint8_t* data = apdu.data() + 1; + + memcpy(data, knxSerialNumber, 6); + memcpy(data + 6, domainAddress, 2); + + //apdu.printPDU(); + + dataBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu, secCtrl); +} + +void ApplicationLayer::propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) +{ + CemiFrame frame(5); + APDU& apdu = frame.apdu(); + apdu.type(PropertyValueRead); + uint8_t* data = apdu.data(); + data += 1; + data = pushByte(objectIndex, data); + data = pushByte(propertyId, data); + pushWord(startIndex & 0xfff, data); + *data &= ((numberOfElements & 0xf) << 4); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::propertyValueReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + propertyDataSend(PropertyValueResponse, ack, priority, hopType, asap, secCtrl, objectIndex, propertyId, numberOfElements, + startIndex, data, length); +} + +void ApplicationLayer::propertyValueExtReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + propertyExtDataSend(PropertyValueExtResponse, ack, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, numberOfElements, + startIndex, data, length); +} + +void ApplicationLayer::propertyValueExtWriteConResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t returnCode) +{ + uint8_t noOfElem = (returnCode != ReturnCodes::Success) ? 0 : numberOfElements; + propertyExtDataSend(PropertyValueExtWriteConResponse, ack, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, noOfElem, + startIndex, &returnCode, 1); +} + +void ApplicationLayer::propertyValueWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + propertyDataSend(PropertyValueWrite, ack, priority, hopType, asap, secCtrl, objectIndex, propertyId, numberOfElements, + startIndex, data, length); +} + +void ApplicationLayer::adcReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t channelNr, uint8_t readCount, int16_t value) +{ + CemiFrame frame(4); + APDU& apdu = frame.apdu(); + apdu.type(ADCResponse); + uint8_t* data = apdu.data(); + + data[0] |= (channelNr & 0b111111); + data[1] = readCount; + data[2] = value >> 8; + data[3] = value & 0xFF; + + if (asap == _connectedTsap) + dataConnectedRequest(asap, priority, apdu, secCtrl); + else + dataIndividualRequest(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::functionPropertyStateResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t* resultData, uint8_t resultLength) +{ + CemiFrame frame(3 + resultLength); + APDU& apdu = frame.apdu(); + apdu.type(FunctionPropertyStateResponse); + uint8_t* data = apdu.data() + 1; + + data[0] = objectIndex; + data[1] = propertyId; + + if (resultLength > 0) + memcpy(&data[2], resultData, resultLength); + + if (asap == _connectedTsap) + dataConnectedRequest(asap, priority, apdu, secCtrl); + else + dataIndividualRequest(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::functionPropertyExtStateResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint16_t propertyId, uint8_t* resultData, uint8_t resultLength) +{ + CemiFrame frame(5 + resultLength + 1); + APDU& apdu = frame.apdu(); + apdu.type(FunctionPropertyExtStateResponse); + uint8_t* data = apdu.data() + 1; + + data[0] = ((uint16_t)objectType) >> 8; + data[1] = ((uint16_t)objectType) & 0xFF; + data[2] = objectInstance >> 4; + data[3] = ((objectInstance & 0x0F) << 4) | (propertyId >> 8); + data[4] = (propertyId & 0xFF); + + // data[5] must contain the return code + if (resultLength > 0) + memcpy(&data[5], resultData, resultLength); + + if (asap == _connectedTsap) + dataConnectedRequest(asap, priority, apdu, secCtrl); + else + dataIndividualRequest(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::propertyDescriptionReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex) +{ + CemiFrame frame(4); + APDU& apdu = frame.apdu(); + apdu.type(PropertyDescriptionRead); + uint8_t* data = apdu.data(); + data[1] = objectIndex; + data[2] = propertyId; + data[3] = propertyIndex; + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access) +{ + CemiFrame frame(8); + APDU& apdu = frame.apdu(); + apdu.type(PropertyDescriptionResponse); + uint8_t* data = apdu.data(); + data[1] = objectIndex; + data[2] = propertyId; + data[3] = propertyIndex; + + if (writeEnable) + data[4] |= 0x80; + + data[4] |= (type & 0x3f); + pushWord(maxNumberOfElements & 0xfff, data + 5); + data[7] = access; + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::propertyExtDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint16_t propertyIndex, uint8_t descriptionType, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access) +{ + CemiFrame frame(16); + APDU& apdu = frame.apdu(); + apdu.type(PropertyExtDescriptionResponse); + uint8_t* data = apdu.data(); + + data[1] = (objectType & 0xff00) >> 8; + data[2] = (objectType & 0x00ff); + + data[3] = (objectInstance & 0x0ff0) >> 4; + data[4] = (objectInstance & 0x000f) << 4 | (propertyId & 0x0f00) >> 8; + data[5] = (propertyId & 0x00ff); + + data[6] = (descriptionType & 0x000f) << 4 | (propertyIndex & 0x0f00) >> 8; + data[7] = (propertyIndex & 0x00ff); + data[8] = 0; // DataPointType ?? + data[9] = 0; // DataPointType ?? + data[10] = 0; // DataPointType ?? + data[11] = 0; // DataPointType ?? + + if (writeEnable) + data[12] |= 0x80; + + data[12] |= (type & 0x3f); + + pushWord(maxNumberOfElements & 0xfff, data + 13); + data[15] = access; + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::memoryReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress) +{ + CemiFrame frame(3); + APDU& apdu = frame.apdu(); + apdu.type(MemoryRead); + uint8_t* data = apdu.data(); + *data |= (number & 0x3f); + pushWord(memoryAddress, data + 1); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::memoryReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData) +{ + memorySend(MemoryResponse, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); +} + +void ApplicationLayer::memoryRouterReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData) +{ + memoryRouterSend(MemoryRouterReadResponse, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); +} + +void ApplicationLayer::memoryRoutingTableReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData) +{ + memoryRoutingTableSend(RoutingTableReadResponse, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); +} + +void ApplicationLayer::memoryExtReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ReturnCodes code, + uint8_t number, uint32_t memoryAddress, uint8_t* memoryData) +{ + CemiFrame frame(5 + number); + APDU& apdu = frame.apdu(); + apdu.type(MemoryExtReadResponse); + uint8_t* data = apdu.data(); + data[1] = code; + data[2] = (memoryAddress >> 16); + data[3] = (memoryAddress >> 8); + data[4] = (memoryAddress & 0xFF); + + memcpy(&data[5], memoryData, number); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::memoryExtWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ReturnCodes code, + uint8_t number, uint32_t memoryAddress, uint8_t* memoryData) +{ + bool withCrc = code == ReturnCodes::SuccessWithCrc; + + CemiFrame frame(5 + (withCrc ? 2 : 0)); + APDU& apdu = frame.apdu(); + apdu.type(MemoryExtWriteResponse); + uint8_t* data = apdu.data(); + data[1] = code; + data[2] = (memoryAddress >> 16); + data[3] = (memoryAddress >> 8); + data[4] = (memoryAddress & 0xFF); + + if (withCrc) + { + uint16_t crc = crc16Ccitt(memoryData, number); + data[5] = crc >> 8; + data[6] = crc & 0xFF; + } + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::memoryWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ + memorySend(MemoryWrite, ack, priority, hopType, asap, secCtrl, number, memoryAddress, data); +} + +void ApplicationLayer::userMemoryReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t number, uint32_t memoryAddress) +{ + CemiFrame frame(4); + APDU& apdu = frame.apdu(); + apdu.type(UserMemoryRead); + uint8_t* data = apdu.data(); + data[1] |= (number & 0xf); + data[1] |= ((memoryAddress >> 12) & 0xf0); + pushWord(memoryAddress & 0xff, data + 2); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::userMemoryReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t number, uint32_t memoryAddress, uint8_t* memoryData) +{ + userMemorySend(UserMemoryResponse, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); +} + +void ApplicationLayer::userMemoryWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t number, uint32_t memoryAddress, uint8_t* memoryData) +{ + userMemorySend(UserMemoryWrite, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); +} + +void ApplicationLayer::userManufacturerInfoReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl) +{ + CemiFrame frame(1); + APDU& apdu = frame.apdu(); + apdu.type(UserManufacturerInfoRead); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::userManufacturerInfoReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t* info) +{ + CemiFrame frame(4); + APDU& apdu = frame.apdu(); + apdu.type(UserMemoryRead); + uint8_t* data = apdu.data(); + memcpy(data + 1, info, 3); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::authorizeRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key) +{ + CemiFrame frame(6); + APDU& apdu = frame.apdu(); + apdu.type(AuthorizeRequest); + uint8_t* data = apdu.data(); + pushInt(key, data + 2); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::authorizeResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level) +{ + CemiFrame frame(2); + APDU& apdu = frame.apdu(); + apdu.type(AuthorizeResponse); + uint8_t* data = apdu.data(); + data[1] = level; + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::keyWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, uint32_t key) +{ + CemiFrame frame(6); + APDU& apdu = frame.apdu(); + apdu.type(KeyWrite); + uint8_t* data = apdu.data(); + data[1] = level; + pushInt(key, data + 2); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::keyWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level) +{ + CemiFrame frame(6); + APDU& apdu = frame.apdu(); + apdu.type(KeyResponse); + uint8_t* data = apdu.data(); + data[1] = level; + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::propertyDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + CemiFrame frame(5 + length); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* apduData = apdu.data(); + apduData += 1; + apduData = pushByte(objectIndex, apduData); + apduData = pushByte(propertyId, apduData); + pushWord(startIndex & 0xfff, apduData); + *apduData |= ((numberOfElements & 0xf) << 4); + apduData += 2; + + if (length > 0) + memcpy(apduData, data, length); + + if (asap == _connectedTsap) + dataConnectedRequest(asap, priority, apdu, secCtrl); + else + dataIndividualRequest(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::propertyExtDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + CemiFrame frame(9 + length); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* apduData = apdu.data(); + apduData += 1; + + apduData[0] = ((uint16_t)objectType) >> 8; + apduData[1] = ((uint16_t)objectType) & 0xFF; + apduData[2] = objectInstance >> 4; + apduData[3] = ((objectInstance & 0x0F) << 4) | (propertyId >> 8); + apduData[4] = (propertyId & 0xFF); + apduData[5] = numberOfElements; + apduData[6] = (startIndex & 0x0FFF) >> 8; + apduData[7] = startIndex & 0xFF; + + if (length > 0) + memcpy(apduData + 8, data, length); + + if (asap == _connectedTsap) + dataConnectedRequest(asap, priority, apdu, secCtrl); + else + dataIndividualRequest(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::groupValueSend(ApduType type, AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* data, uint8_t& dataLength) +{ + if (_assocTable == nullptr) + return; + + CemiFrame frame(dataLength + 1); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* apdudata = apdu.data(); + + if (dataLength == 0) + { + // data size is six bit or less. So store in first byte + *apdudata &= ~0x3f; + *apdudata |= (*data & 0x3f); + } + else + { + memcpy(apdudata + 1, data, dataLength); + } + + // no need to check if there is a tsap. This is a response, so the read got through + uint16_t tsap = (uint16_t)_assocTable->translateAsap(asap); + dataGroupRequest(ack, hopType, priority, tsap, apdu, secCtrl); + dataGroupIndication(hopType, priority, tsap, apdu, secCtrl); +} + +void ApplicationLayer::memorySend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData) +{ + CemiFrame frame(3 + number); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* data = apdu.data(); + *data |= (number & 0x3f); + pushWord(memoryAddress, data + 1); + + if (number > 0) + memcpy(data + 3, memoryData, number); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::memoryRouterSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData) +{ + CemiFrame frame(4 + number); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* data = apdu.data(); + data[1] |= (number & 0xf); + pushWord(memoryAddress & 0xffff, data + 2); + + if (number > 0) + memcpy(data + 4, memoryData, number); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::memoryRoutingTableSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData) +{ + CemiFrame frame(4 + number); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* data = apdu.data(); + data[1] |= (number & 0xf); + pushWord(memoryAddress & 0xffff, data + 2); + + if (number > 0) + memcpy(data + 4, memoryData, number); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::userMemorySend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData) +{ + CemiFrame frame(4 + number); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* data = apdu.data(); + data[1] |= (number & 0xf); + data[1] |= ((memoryAddress >> 12) & 0xf0); + pushWord(memoryAddress & 0xffff, data + 2); + + if (number > 0) + memcpy(data + 4, memoryData, number); + + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::individualIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl) +{ + uint8_t* data = apdu.data(); + + switch (apdu.type()) + { + case DeviceDescriptorRead: + _bau.deviceDescriptorReadIndication(priority, hopType, tsap, secCtrl, *data & 0x3f); + break; + + case DeviceDescriptorResponse: + _bau.deviceDescriptorReadAppLayerConfirm(priority, hopType, tsap, secCtrl, *data & 0x3f, data + 1); + break; + + case Restart: + case RestartMasterReset: + { + // These reserved bits must be 0 + uint8_t reservedBits = data[0] & 0x1e; + + if (reservedBits != 0) + return; + + // handle erase code for factory reset (setting FDSK again as toolkey, etc.) + RestartType restartType = (RestartType) (data[0] & 0x3f); + EraseCode eraseCode = EraseCode::Void; + uint8_t channel = 0; + + if (restartType == RestartType::MasterReset) + { + eraseCode = (EraseCode) data[1]; + channel = data[2]; + } + + _bau.restartRequestIndication(priority, hopType, tsap, secCtrl, restartType, eraseCode, channel); + break; + } + + case PropertyValueRead: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadIndication(priority, hopType, tsap, secCtrl, data[1], data[2], data[3] >> 4, startIndex); + break; + } + + case PropertyValueResponse: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5); + break; + } + + case PropertyValueWrite: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueWriteIndication(priority, hopType, tsap, secCtrl, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5); + break; + } + + case PropertyValueExtRead: + { + ObjectType objectType = (ObjectType)(((data[1] & 0xff) << 8) | (data[2] & 0xff)); + uint8_t objectInstance = ((data[3] & 0xff) << 4) | ((data[4] & 0xff) >> 4); + uint16_t propertyId = ((data[4] & 0xf) << 8) | (data[5] & 0xff); + uint8_t numberOfElements = data[6]; + uint16_t startIndex = ((data[7] & 0xf) << 8) | (data[8] & 0xff); + _bau.propertyValueExtReadIndication(priority, hopType, tsap, secCtrl, objectType, objectInstance, propertyId, numberOfElements, startIndex); + break; + } + + case PropertyValueExtWriteCon: + case PropertyValueExtWriteUnCon: + { + ObjectType objectType = (ObjectType)(((data[1] & 0xff) << 8) | (data[2] & 0xff)); + uint8_t objectInstance = ((data[3] & 0xff) << 4) | ((data[4] & 0xff) >> 4); + uint16_t propertyId = ((data[4] & 0xf) << 8) | (data[5] & 0xff); + uint8_t numberOfElements = data[6]; + uint16_t startIndex = ((data[7] & 0xf) << 8) | (data[8] & 0xff); + bool confirmed = (apdu.type() == PropertyValueExtWriteCon); + _bau.propertyValueExtWriteIndication(priority, hopType, tsap, secCtrl, objectType, objectInstance, propertyId, numberOfElements, startIndex, + data + 9, apdu.length() - 9, confirmed); + break; + } + + case FunctionPropertyCommand: + _bau.functionPropertyCommandIndication(priority, hopType, tsap, secCtrl, data[1], data[2], &data[3], apdu.length() - 3); //TODO: check length + break; + + case FunctionPropertyState: + _bau.functionPropertyStateIndication(priority, hopType, tsap, secCtrl, data[1], data[2], &data[3], apdu.length() - 3); //TODO: check length + break; + + case FunctionPropertyExtCommand: + { + ObjectType objectType = (ObjectType)(((data[1] & 0xff) << 8) | (data[2] & 0xff)); + uint8_t objectInstance = ((data[3] & 0xff) << 4) | ((data[4] & 0xff) >> 4); + uint16_t propertyId = ((data[4] & 0xf) << 8) | (data[5] & 0xff); + uint8_t* functionInput = &data[6]; + _bau.functionPropertyExtCommandIndication(priority, hopType, tsap, secCtrl, objectType, objectInstance, propertyId, functionInput, apdu.length() - 6); + break; + } + + case FunctionPropertyExtState: + { + ObjectType objectType = (ObjectType)(((data[1] & 0xff) << 8) | (data[2] & 0xff)); + uint8_t objectInstance = ((data[3] & 0xff) << 4) | ((data[4] & 0xff) >> 4); + uint16_t propertyId = ((data[4] & 0xf) << 8) | (data[5] & 0xff); + uint8_t* functionInput = &data[6]; + _bau.functionPropertyExtStateIndication(priority, hopType, tsap, secCtrl, objectType, objectInstance, propertyId, functionInput, apdu.length() - 6); + break; + } + + case PropertyDescriptionRead: + _bau.propertyDescriptionReadIndication(priority, hopType, tsap, secCtrl, data[1], data[2], data[3]); + break; + + case PropertyExtDescriptionRead: + { + ObjectType objectType = (ObjectType)(((data[1] & 0xff) << 8) | (data[2] & 0xff)); + uint16_t objectInstance = ((data[3] & 0xff) << 4) | ((data[4] & 0xf0) >> 4); + uint16_t propertyId = ((data[4] & 0x0f) << 8) | (data[5] & 0xff); + uint8_t descriptionType = (data[6] & 0xf0) >> 4; + uint16_t propertyIndex = ((data[7] & 0x0f) << 8) | (data[8] & 0xff); + + _bau.propertyExtDescriptionReadIndication(priority, hopType, tsap, secCtrl, objectType, objectInstance, propertyId, descriptionType, propertyIndex); + break; + } + + case PropertyDescriptionResponse: + _bau.propertyDescriptionReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1], data[2], data[3], + (data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7]); + break; + + case MemoryRead: + _bau.memoryReadIndication(priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1)); + break; + + case MemoryResponse: + _bau.memoryReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3); + break; + + case MemoryWrite: + _bau.memoryWriteIndication(priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3); + break; + + // EC + case MemoryRouterWrite: + print("MemoryRouterWrite: "); + _bau.memoryRouterWriteIndication(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + + case MemoryRouterReadResponse: + _bau.memoryRouterReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + + case RoutingTableOpen: + println("Received OpenRoutingTable APDU, doing nothing"); + break; + + case RoutingTableRead: + _bau.memoryRoutingTableReadIndication(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2)); + break; + + case RoutingTableReadResponse: + _bau.memoryRoutingTableReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + + case RoutingTableWrite: + _bau.memoryRoutingTableWriteIndication(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + + // end EC + + case MemoryExtRead: + { + uint8_t number = data[1]; + uint32_t memoryAddress = ((data[2] & 0xff) << 16) | ((data[3] & 0xff) << 8) | (data[4] & 0xff); + _bau.memoryExtReadIndication(priority, hopType, tsap, secCtrl, number, memoryAddress); + break; + } + + //case MemoryExtReadResponse: + // _bau.memoryExtReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[0], getInt(data + 1), data + 4); // TODO return code + // break; + case MemoryExtWrite: + { + uint8_t number = data[1]; + uint32_t memoryAddress = ((data[2] & 0xff) << 16) | ((data[3] & 0xff) << 8) | (data[4] & 0xff); + _bau.memoryExtWriteIndication(priority, hopType, tsap, secCtrl, number, memoryAddress, data + 5); + break; + } + + //case MemoryExtWriteResponse: + // _bau.memoryExtWriteAppLayerConfirm(priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3); // TODO return code + // break; + case UserMemoryRead: + { + uint32_t address = ((data[1] & 0xf0) << 12) + (data[2] << 8) + data[3]; + _bau.userMemoryReadIndication(priority, hopType, tsap, secCtrl, data[1] & 0xf, address); + break; + } + + case UserMemoryResponse: + { + uint32_t address = ((data[1] & 0xf0) << 12) + (data[2] << 8) + data[3]; + _bau.userMemoryReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1] & 0xf, address, data + 4); + break; + } + + case UserMemoryWrite: + { + uint32_t address = ((data[1] & 0xf0) << 12) + (data[2] << 8) + data[3]; + _bau.userMemoryWriteIndication(priority, hopType, tsap, secCtrl, data[1] & 0xf, address, data + 4); + break; + } + + case UserManufacturerInfoRead: + _bau.userManufacturerInfoIndication(priority, hopType, tsap, secCtrl); + break; + + case UserManufacturerInfoResponse: + _bau.userManufacturerInfoAppLayerConfirm(priority, hopType, tsap, secCtrl, data + 1); + break; + + case AuthorizeRequest: + _bau.authorizeIndication(priority, hopType, tsap, secCtrl, getInt(data + 2)); + break; + + case AuthorizeResponse: + _bau.authorizeAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1]); + break; + + case KeyWrite: + _bau.keyWriteIndication(priority, hopType, tsap, secCtrl, data[1], getInt(data + 2)); + break; + + case KeyResponse: + _bau.keyWriteAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1]); + break; + + case ADCRead: + { + //Since we don't have an adc for bus voltage, we just send zero as readCount + uint8_t channelNr = data[0] & 0b111111; + this->adcReadResponse(AckRequested, priority, hopType, tsap, secCtrl, channelNr, 0, 0); + break; + } + + default: + print("Individual-indication: unhandled APDU-Type: "); + apdu.printPDU(); + } +} + +void ApplicationLayer::individualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl, bool status) +{ + uint8_t* data = apdu.data(); + + switch (apdu.type()) + { + case DeviceDescriptorRead: + _bau.deviceDescriptorReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, *data & 0x3f, status); + break; + + case DeviceDescriptorResponse: + _bau.deviceDescriptorReadResponseConfirm(ack, priority, hopType, tsap, secCtrl, *data & 0x3f, data + 1, status); + break; + + case Restart: + _bau.restartRequestLocalConfirm(ack, priority, hopType, tsap, secCtrl, status); + break; + + case PropertyValueRead: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3] >> 4, + startIndex, status); + break; + } + + case PropertyValueResponse: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5, status); + break; + } + + case PropertyValueWrite: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueWriteLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5, status); + break; + } + + case PropertyDescriptionRead: + _bau.propertyDescriptionReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3], status); + break; + + case PropertyExtDescriptionRead: + _bau.propertyExtDescriptionReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3], status); + break; + + case PropertyDescriptionResponse: + _bau.propertyDescriptionReadResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3], + (data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7], status); + break; + + case MemoryRead: + _bau.memoryReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), status); + break; + + case MemoryResponse: + _bau.memoryReadResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3, status); + break; + + case MemoryWrite: + _bau.memoryWriteLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3, status); + break; + + case MemoryExtRead: + _bau.memoryExtReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), status); + break; + + case MemoryExtReadResponse: + _bau.memoryExtReadResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3, status); + break; + + case MemoryExtWrite: + _bau.memoryExtWriteLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3, status); + break; + + case MemoryExtWriteResponse: + _bau.memoryExtWriteResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3, status); + break; + + case UserMemoryRead: + { + uint32_t address = ((data[1] & 0xf0) << 12) + (data[2] << 8) + data[3]; + _bau.memoryReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1] & 0xf, address, status); + break; + } + + case UserMemoryResponse: + { + uint32_t address = ((data[1] & 0xf0) << 12) + (data[2] << 8) + data[3]; + _bau.memoryReadResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[1] & 0xf, address, data + 4, status); + break; + } + + case UserMemoryWrite: + { + uint32_t address = ((data[1] & 0xf0) << 12) + (data[2] << 8) + data[3]; + _bau.memoryWriteLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1] & 0xf, address, data + 4, status); + break; + } + + case UserManufacturerInfoRead: + _bau.userManufacturerInfoLocalConfirm(ack, priority, hopType, tsap, secCtrl, status); + break; + + case UserManufacturerInfoResponse: + _bau.userManufacturerInfoResponseConfirm(ack, priority, hopType, tsap, secCtrl, data + 1, status); + break; + + case AuthorizeRequest: + _bau.authorizeLocalConfirm(ack, priority, hopType, tsap, secCtrl, getInt(data + 2), status); + break; + + case AuthorizeResponse: + _bau.authorizeResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[1], status); + break; + + case KeyWrite: + _bau.keyWriteLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1], getInt(data + 2), status); + break; + + case KeyResponse: + _bau.keyWriteResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[1], status); + break; + + default: + print("Individual-confirm: unhandled APDU-Type: "); + println(apdu.type()); + } +} + +void ApplicationLayer::individualSend(AckType ack, HopCountType hopType, Priority priority, uint16_t asap, APDU& apdu, const SecurityControl& secCtrl) +{ + if (asap == _connectedTsap) + dataConnectedRequest(asap, priority, apdu, secCtrl); + else + dataIndividualRequest(ack, hopType, priority, asap, apdu, secCtrl); +} + +bool ApplicationLayer::isConnected() +{ + return (_connectedTsap >= 0); +} + +void ApplicationLayer::dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl) +{ + (void)secCtrl; // We do not need security related information in the plain application layer + _transportLayer->dataGroupRequest(ack, hopType, priority, tsap, apdu); +} +void ApplicationLayer::dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl) +{ + (void)secCtrl; // We do not need security related information in the plain application layer + _transportLayer->dataBroadcastRequest(ack, hopType, SystemPriority, apdu); +} +void ApplicationLayer::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl) +{ + (void)secCtrl; // We do not need security related information in the plain application layer + _transportLayer->dataSystemBroadcastRequest(ack, hopType, SystemPriority, apdu); +} +void ApplicationLayer::dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu, const SecurityControl& secCtrl) +{ + (void)secCtrl; // We do not need security related information in the plain application layer + _transportLayer->dataIndividualRequest(ack, hopType, priority, destination, apdu); +} +void ApplicationLayer::dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu, const SecurityControl& secCtrl) +{ + (void)secCtrl; // We do not need security related information in the plain application layer + // apdu must be valid until it was confirmed + _transportLayer->dataConnectedRequest(tsap, priority, apdu); +} diff --git a/components/knx/src/knx/application_layer.h b/components/knx/src/knx/application_layer.h new file mode 100644 index 0000000..9f8f2c5 --- /dev/null +++ b/components/knx/src/knx/application_layer.h @@ -0,0 +1,228 @@ +#pragma once + +#include +#include "knx_types.h" +#include "apdu.h" + +class AssociationTableObject; +class BusAccessUnit; +class TransportLayer; +/** + * This is an implementation of the application layer as specified in @cite knx:3/5/1. + * It provides methods for the BusAccessUnit to do different things and translates this + * call to an APDU and calls the correct method of the TransportLayer. + * It also takes calls from TransportLayer, decodes the submitted APDU and calls the corresponding + * methods of the BusAccessUnit class. + */ +class ApplicationLayer +{ + public: + /** + * The constructor. + * @param assocTable The AssociationTable is used to translate between asap (i.e. group objects) and group addresses. + * @param bau methods are called here depending of the content of the APDU + */ + ApplicationLayer(BusAccessUnit& bau); + /** + * Assigns the TransportLayer to which encoded APDU are submitted to. + */ + void transportLayer(TransportLayer& layer); + + void associationTableObject(AssociationTableObject& assocTable); + + // from transport layer + // Note: without data secure feature, the application layer is just used with SecurityControl.dataSecurity = None + // hooks that can be implemented by derived class (e.g. SecureApplicationLayer) + + #pragma region Transport - Layer - Callbacks + /** + * Somebody send us an APDU via multicast communication. See 3.2 of @cite knx:3/3/4. + * See also ApplicationLayer::dataGroupConfirm and TransportLayer::dataGroupRequest. + * This method is called by the TransportLayer. + * + * @param tsap used the find the correct GroupObject with the help of the AssociationTableObject. + * See 3.1.1 of @cite knx:3/3/7 + * + * @param apdu The submitted APDU. + * + * @param priority The ::Priority of the received request. + * + * @param hopType Should routing be endless or should the NetworkLayer::hopCount be used? See also ::HopCountType. + */ + virtual void dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu); + /** + * Report the status of an APDU that we sent via multicast communication back to us. See 3.2 of @cite knx:3/3/4. + * See also ApplicationLayer::dataGroupConfirm and TransportLayer::dataGroupRequest. This method is called by + * the TransportLayer. + * + * @param tsap used the find the correct GroupObject with the help of the AssociationTableObject. + * See 3.1.1 of @cite knx:3/3/7 + * + * @param apdu The submitted APDU. + * + * @param priority The ::Priority of the received request. + * + * @param hopType Should routing be endless or should the NetworkLayer::hopCount be used? See also ::HopCountType. + * + * @param status Was the request successful? + * + * @param ack Did we want a DataLinkLayer acknowledgement? See ::AckType. + */ + virtual void dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status); + virtual void dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu); + virtual void dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, bool status); + virtual void dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu); + virtual void dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status); + virtual void dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu); + virtual void dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status); + virtual void dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu); + virtual void dataConnectedConfirm(uint16_t tsap); + void connectIndication(uint16_t tsap); + void connectConfirm(uint16_t destination, uint16_t tsap, bool status); + void disconnectIndication(uint16_t tsap); + void disconnectConfirm(Priority priority, uint16_t tsap, bool status); + #pragma endregion + + #pragma region from bau + void groupValueReadRequest(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl); + void groupValueReadResponse(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength); + void groupValueWriteRequest(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength); + void individualAddressWriteRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint16_t newaddress); + void individualAddressReadRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl); + void individualAddressReadResponse(AckType ack, HopCountType hopType, const SecurityControl& secCtrl); + void individualAddressSerialNumberReadRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber); + void individualAddressSerialNumberReadResponse(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, + uint16_t domainAddress); + void individualAddressSerialNumberWriteRequest(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, + uint16_t newaddress); + void deviceDescriptorReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t descriptorType); + void deviceDescriptorReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t descriptorType, uint8_t* deviceDescriptor); + void connectRequest(uint16_t destination, Priority priority); + void disconnectRequest(Priority priority); + bool isConnected(); + void restartRequest(AckType ack, Priority priority, HopCountType hopType, const SecurityControl& secCtrl); + void restartResponse(AckType ack, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t errorCode, uint16_t processTime); + void propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex); + void propertyValueReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + void propertyValueExtReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + void propertyValueExtWriteConResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t returnCode); + void propertyValueWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + void adcReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t channelNr, uint8_t readCount, int16_t value); + void functionPropertyStateResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t* resultData, uint8_t resultLength); + void functionPropertyExtStateResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint16_t propertyId, uint8_t* resultData, uint8_t resultLength); + void propertyDescriptionReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex); + void propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access); + void propertyExtDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint16_t propertyIndex, uint8_t descriptionType, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access); + void memoryReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress); + void memoryReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryRouterReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryRoutingTableReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryExtReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ReturnCodes code, uint8_t number, + uint32_t memoryAddress, uint8_t* data); + void memoryExtWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ReturnCodes code, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData); + void memoryWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void userMemoryReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress); + void userMemoryReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData); + void userMemoryWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData); + void userManufacturerInfoReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl); + void userManufacturerInfoReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t* info); + void authorizeRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key); + void authorizeResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level); + void keyWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, uint32_t key); + void keyWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level); + + void systemNetworkParameterReadResponse(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength, + uint8_t* testResult, uint16_t testResultLength); + void domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber); + void IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* domainAddress, + const uint8_t* knxSerialNumber); + #pragma endregion + + protected: + #pragma region hooks + void dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl); + void dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, + APDU& apdu, const SecurityControl& secCtrl, bool status); + void dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, const SecurityControl& secCtrl); + void dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl, bool status); + void dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, const SecurityControl& secCtrl); + void dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl, bool status); + void dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, const SecurityControl& secCtrl); + void dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl, bool status); + void dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl); + void dataConnectedConfirm(uint16_t tsap, const SecurityControl& secCtrl); + #pragma endregion + + // to transport layer + virtual void dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl); + virtual void dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl); + virtual void dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl); + virtual void dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu, const SecurityControl& secCtrl); + virtual void dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu, const SecurityControl& secCtrl); // apdu must be valid until it was confirmed + + uint16_t getConnectedTsasp() + { + return _connectedTsap; + } + + // Protected: we need to access it in derived class SecureApplicationLayer + TransportLayer* _transportLayer = 0; + + static const SecurityControl noSecurity; + + private: + void propertyDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, + uint8_t length); + void propertyExtDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + void memorySend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData); + // Added EC + void memoryRouterSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData); + void memoryRoutingTableSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData); + // + void userMemorySend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t number, uint32_t memoryAddress, uint8_t* memoryData); + void groupValueSend(ApduType type, AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t& dataLength); + void individualIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl); + void individualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl, bool status); + void individualSend(AckType ack, HopCountType hopType, Priority priority, uint16_t asap, APDU& apdu, const SecurityControl& secCtrl); + + uint16_t _savedAsapReadRequest = 0; + uint16_t _savedAsapWriteRequest = 0; + uint16_t _savedAsapResponse = 0; + AssociationTableObject* _assocTable = nullptr; + BusAccessUnit& _bau; + + int32_t _connectedTsap = -1; +}; diff --git a/components/knx/src/knx/application_program_object.cpp b/components/knx/src/knx/application_program_object.cpp new file mode 100644 index 0000000..c885f3d --- /dev/null +++ b/components/knx/src/knx/application_program_object.cpp @@ -0,0 +1,99 @@ +#include "application_program_object.h" +#include "bits.h" +#include "data_property.h" +#include "callback_property.h" +#include "dptconvert.h" +#include + +ApplicationProgramObject::ApplicationProgramObject(Memory& memory) +#if MASK_VERSION == 0x091A + : TableObject(memory, 0x0100, 0x0100) +#else + : TableObject(memory) +#endif +{ + Property* properties[] = + { + new DataProperty(PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_APPLICATION_PROG), + new DataProperty(PID_PROG_VERSION, true, PDT_GENERIC_05, 1, ReadLv3 | WriteLv3), + new CallbackProperty(this, PID_PEI_TYPE, false, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv0, + [](ApplicationProgramObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + data[0] = 0; + return 1; + }) + }; + + TableObject::initializeProperties(sizeof(properties), properties); +} + +uint8_t* ApplicationProgramObject::save(uint8_t* buffer) +{ + uint8_t programVersion[5]; + property(PID_PROG_VERSION)->read(programVersion); + buffer = pushByteArray(programVersion, 5, buffer); + + return TableObject::save(buffer); +} + +const uint8_t* ApplicationProgramObject::restore(const uint8_t* buffer) +{ + uint8_t programVersion[5]; + buffer = popByteArray(programVersion, 5, buffer); + property(PID_PROG_VERSION)->write(programVersion); + + return TableObject::restore(buffer); +} + +uint16_t ApplicationProgramObject::saveSize() +{ + return TableObject::saveSize() + 5; // sizeof(programVersion) +} + +uint8_t* ApplicationProgramObject::data(uint32_t addr) +{ + return TableObject::data() + addr; +} + +uint8_t ApplicationProgramObject::getByte(uint32_t addr) +{ + return *(TableObject::data() + addr); +} + +uint16_t ApplicationProgramObject::getWord(uint32_t addr) +{ + return ::getWord(TableObject::data() + addr); +} + +uint32_t ApplicationProgramObject::getInt(uint32_t addr) +{ + return ::getInt(TableObject::data() + addr); +} + +double ApplicationProgramObject::getFloat(uint32_t addr, ParameterFloatEncodings encoding) +{ + switch (encoding) + { + case Float_Enc_DPT9: + return float16FromPayload(TableObject::data() + addr, 0); + break; + + case Float_Enc_IEEE754Single: + return float32FromPayload(TableObject::data() + addr, 0); + break; + + case Float_Enc_IEEE754Double: + return float64FromPayload(TableObject::data() + addr, 0); + break; + + default: + return 0; + break; + } +} diff --git a/components/knx/src/knx/application_program_object.h b/components/knx/src/knx/application_program_object.h new file mode 100644 index 0000000..dc20bb3 --- /dev/null +++ b/components/knx/src/knx/application_program_object.h @@ -0,0 +1,18 @@ +#pragma once + +#include "table_object.h" +#include "bits.h" + +class ApplicationProgramObject : public TableObject +{ + public: + ApplicationProgramObject(Memory& memory); + uint8_t* save(uint8_t* buffer) override; + const uint8_t* restore(const uint8_t* buffer) override; + uint16_t saveSize() override; + uint8_t* data(uint32_t addr); + uint8_t getByte(uint32_t addr); + uint16_t getWord(uint32_t addr); + uint32_t getInt(uint32_t addr); + double getFloat(uint32_t addr, ParameterFloatEncodings encoding); +}; diff --git a/components/knx/src/knx/association_table_object.cpp b/components/knx/src/knx/association_table_object.cpp new file mode 100644 index 0000000..7eebca2 --- /dev/null +++ b/components/knx/src/knx/association_table_object.cpp @@ -0,0 +1,174 @@ +#include + +#include "association_table_object.h" +#include "bits.h" +#include "data_property.h" + +using namespace std; + +AssociationTableObject::AssociationTableObject(Memory& memory) + : TableObject(memory) +{ + Property* properties[] = + { + new DataProperty(PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_ASSOC_TABLE), + new DataProperty(PID_TABLE, false, PDT_GENERIC_04, 65535, ReadLv3 | WriteLv0) //FIXME: implement correctly + }; + + TableObject::initializeProperties(sizeof(properties), properties); +} + +uint16_t AssociationTableObject::entryCount() +{ + return ntohs(_tableData[0]); +} + +uint16_t AssociationTableObject::getTSAP(uint16_t idx) +{ + if (idx >= entryCount()) + return 0; + + return ntohs(_tableData[2 * idx + 1]); +} + +uint16_t AssociationTableObject::getASAP(uint16_t idx) +{ + if (idx >= entryCount()) + return 0; + + return ntohs(_tableData[2 * idx + 2]); +} + +// after any table change the table is checked if it allows +// binary search access. If not, sortedEntryCount stays 0, +// otherwise sortedEntryCount represents size of bin search array +void AssociationTableObject::prepareBinarySearch() +{ + sortedEntryCount = 0; +#ifdef USE_BINSEARCH + uint16_t lastASAP = 0; + uint16_t currentASAP = 0; + uint16_t lookupIdx = 0; + uint16_t lookupASAP = 0; + + // we iterate through all ASAP + // the first n ASAP are sorted (strictly increasing number), these are assigning sending TSAP + // the remaining ASAP have to be all repetitions, otherwise we set sortedEntryCount to 0, which forces linear search + if (_tableData != nullptr) + { + for (uint16_t idx = 0; idx < entryCount(); idx++) + { + currentASAP = getASAP(idx); + + if (sortedEntryCount) + { + // look if the remaining ASAP exist in the previously sorted list. + while (lookupIdx < sortedEntryCount) + { + lookupASAP = getASAP(lookupIdx); + + if (currentASAP <= lookupASAP) + break; // while + else + lookupIdx++; + } + + if (currentASAP < lookupASAP || lookupIdx >= sortedEntryCount) + { + // a new ASAP found, we force linear search + sortedEntryCount = 0; + break; // for + } + } + else + { + // check for strictly increasing ASAP + if (currentASAP > lastASAP) + lastASAP = currentASAP; + else + { + sortedEntryCount = idx; // last found index indicates end of sorted list + idx--; // current item has to be handled as remaining ASAP + } + } + } + + // in case complete table is strictly increasing + if (lookupIdx == 0 && sortedEntryCount == 0) + sortedEntryCount = entryCount(); + } + +#endif +} + +const uint8_t* AssociationTableObject::restore(const uint8_t* buffer) +{ + buffer = TableObject::restore(buffer); + _tableData = (uint16_t*)data(); + prepareBinarySearch(); + return buffer; +} + +// return type is int32 so that we can return uint16 and -1 +int32_t AssociationTableObject::translateAsap(uint16_t asap) +{ + // sortedEntryCount is determined in prepareBinarySearch() + // if ETS provides strictly increasing numbers for ASAP + // represents the size of the array to search + if (sortedEntryCount) + { + uint16_t low = 0; + uint16_t high = sortedEntryCount - 1; + + while (low <= high) + { + uint16_t i = (low + high) / 2; + uint16_t asap_i = getASAP(i); + + if (asap_i == asap) + return getTSAP(i); + + if (asap_i > asap) + high = i - 1; + else + low = i + 1 ; + } + } + else + { + // if ASAP numbers are not strictly increasing linear seach is used + for (uint16_t i = 0; i < entryCount(); i++) + if (getASAP(i) == asap) + return getTSAP(i); + } + + return -1; +} + +void AssociationTableObject::beforeStateChange(LoadState& newState) +{ + TableObject::beforeStateChange(newState); + + if (newState != LS_LOADED) + return; + + _tableData = (uint16_t*)data(); + prepareBinarySearch(); +} + +int32_t AssociationTableObject::nextAsap(uint16_t tsap, uint16_t& startIdx) +{ + uint16_t entries = entryCount(); + + for (uint16_t i = startIdx; i < entries; i++) + { + startIdx = i + 1; + + if (getTSAP(i) == tsap) + { + return getASAP(i); + } + } + + return -1; +} diff --git a/components/knx/src/knx/association_table_object.h b/components/knx/src/knx/association_table_object.h new file mode 100644 index 0000000..7465de6 --- /dev/null +++ b/components/knx/src/knx/association_table_object.h @@ -0,0 +1,25 @@ +#pragma once + +#include "table_object.h" + +class AssociationTableObject : public TableObject +{ + public: + AssociationTableObject(Memory& memory); + + const uint8_t* restore(const uint8_t* buffer) override; + + int32_t translateAsap(uint16_t asap); + int32_t nextAsap(uint16_t tsap, uint16_t& startIdx); + + protected: + void beforeStateChange(LoadState& newState) override; + + private: + uint16_t entryCount(); + uint16_t getTSAP(uint16_t idx); + uint16_t getASAP(uint16_t idx); + void prepareBinarySearch(); + uint16_t* _tableData = 0; + uint16_t sortedEntryCount; +}; diff --git a/components/knx/src/knx/bau.cpp b/components/knx/src/knx/bau.cpp new file mode 100644 index 0000000..e481016 --- /dev/null +++ b/components/knx/src/knx/bau.cpp @@ -0,0 +1,392 @@ +#include "bau.h" + +void BusAccessUnit::groupValueReadLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, bool status) +{ +} + +void BusAccessUnit::groupValueReadIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl) +{ +} + +void BusAccessUnit::groupValueReadResponseConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopTtype, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength, bool status) +{ +} + +void BusAccessUnit::groupValueReadAppLayerConfirm(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength) +{ +} + +void BusAccessUnit::groupValueWriteLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength, bool status) +{ +} + +void BusAccessUnit::groupValueWriteIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength) +{ +} + +void BusAccessUnit::individualAddressWriteLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint16_t newaddress, bool status) +{ +} + +void BusAccessUnit::individualAddressWriteIndication(HopCountType hopType, const SecurityControl& secCtrl, uint16_t newaddress) +{ +} + +void BusAccessUnit::individualAddressReadLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, bool status) +{ +} + +void BusAccessUnit::individualAddressReadIndication(HopCountType hopType, const SecurityControl& secCtrl) +{ +} + +void BusAccessUnit::individualAddressReadResponseConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, bool status) +{ +} + +void BusAccessUnit::individualAddressReadAppLayerConfirm(HopCountType hopType, const SecurityControl& secCtrl, uint16_t individualAddress) +{ +} + +void BusAccessUnit::individualAddressSerialNumberReadLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, bool status) +{ +} + +void BusAccessUnit::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* knxSerialNumber) +{ +} + +void BusAccessUnit::individualAddressSerialNumberReadResponseConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, uint16_t domainAddress, bool status) +{ +} + +void BusAccessUnit::individualAddressSerialNumberReadAppLayerConfirm(HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, uint16_t individualAddress, uint16_t domainAddress) +{ +} + +void BusAccessUnit::individualAddressSerialNumberWriteLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, uint16_t newaddress, bool status) +{ +} + +void BusAccessUnit::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber) +{ +} + +void BusAccessUnit::deviceDescriptorReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t descriptorType, bool status) +{ +} + +void BusAccessUnit::deviceDescriptorReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t descriptorType) +{ +} + +void BusAccessUnit::deviceDescriptorReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t descriptor_type, + uint8_t* device_descriptor, bool status) +{ +} + +void BusAccessUnit::deviceDescriptorReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t descriptortype, uint8_t* deviceDescriptor) +{ +} + +void BusAccessUnit::restartRequestLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, bool status) +{ +} + +void BusAccessUnit::restartRequestIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, RestartType restartType, EraseCode eraseCode, uint8_t channel) +{ +} + +void BusAccessUnit::propertyValueReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, bool status) +{ +} + +void BusAccessUnit::propertyValueReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) +{ +} + +void BusAccessUnit::propertyValueExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) +{ +} + +void BusAccessUnit::functionPropertyCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t* data, uint8_t length) +{ +} + +void BusAccessUnit::functionPropertyStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t* data, uint8_t length) +{ +} + +void BusAccessUnit::functionPropertyExtCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t* data, uint8_t length) +{ +} + +void BusAccessUnit::functionPropertyExtStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t* data, uint8_t length) +{ +} + +void BusAccessUnit::propertyValueReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool status) +{ +} + +void BusAccessUnit::propertyValueReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ +} + +void BusAccessUnit::propertyValueWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool status) +{ +} + +void BusAccessUnit::propertyValueWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ +} + +void BusAccessUnit::propertyValueExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool confirmed) +{ +} + +void BusAccessUnit::propertyDescriptionReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool status) +{ +} + +void BusAccessUnit::propertyExtDescriptionReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint16_t objectIndex, uint8_t propertyId, uint16_t propertyIndex, bool status) +{ +} + +void BusAccessUnit::propertyDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex) +{ +} + +void BusAccessUnit::propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex) +{ +} + +void BusAccessUnit::propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, uint16_t maxNumberOfElements, uint8_t access) +{ +} + +void BusAccessUnit::propertyDescriptionReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, uint16_t maxNumberOfElements, uint8_t access, bool status) +{ +} + +void BusAccessUnit::propertyDescriptionReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, uint16_t maxNumberOfElements, uint8_t access) +{ +} + +void BusAccessUnit::memoryReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, bool status) +{ +} + +void BusAccessUnit::memoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress) +{ +} + +void BusAccessUnit::memoryReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data, bool status) +{ +} + +void BusAccessUnit::memoryReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} + +void BusAccessUnit::memoryWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data, bool status) +{ +} + +void BusAccessUnit::memoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} + +void BusAccessUnit::memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} +void BusAccessUnit::memoryRouterReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} +void BusAccessUnit::memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress) +{ +} +void BusAccessUnit::memoryRoutingTableReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} +void BusAccessUnit::memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} + +void BusAccessUnit::memoryExtReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, bool status) +{ +} + +void BusAccessUnit::memoryExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress) +{ +} + +void BusAccessUnit::memoryExtReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data, bool status) +{ +} + +void BusAccessUnit::memoryExtReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data) +{ +} + +void BusAccessUnit::memoryExtWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data, bool status) +{ +} + +void BusAccessUnit::memoryExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data) +{ +} + +void BusAccessUnit::memoryExtWriteResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data, bool status) +{ +} + +void BusAccessUnit::memoryExtWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data) +{ +} + +void BusAccessUnit::userMemoryReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, bool status) +{ +} + +void BusAccessUnit::userMemoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress) +{ +} + +void BusAccessUnit::userMemoryReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* memoryData, bool status) +{ +} + +void BusAccessUnit::userMemoryReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* memoryData) +{ +} + +void BusAccessUnit::userMemoryWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* memoryData, bool status) +{ +} + +void BusAccessUnit::userMemoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* memoryData) +{ +} + +void BusAccessUnit::userManufacturerInfoLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, bool status) +{ +} + +void BusAccessUnit::userManufacturerInfoIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl) +{ +} + +void BusAccessUnit::userManufacturerInfoResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t* info, bool status) +{ +} + +void BusAccessUnit::userManufacturerInfoAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t* info) +{ +} + +void BusAccessUnit::authorizeLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key, bool status) +{ +} + +void BusAccessUnit::authorizeIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key) +{ +} + +void BusAccessUnit::authorizeResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, bool status) +{ +} + +void BusAccessUnit::authorizeAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level) +{ +} + +void BusAccessUnit::keyWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, uint32_t key, bool status) +{ +} + +void BusAccessUnit::keyWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, uint32_t key) +{ +} + +void BusAccessUnit::keyWriteResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, bool status) +{ +} + +void BusAccessUnit::keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level) +{ +} + +void BusAccessUnit::connectConfirm(uint16_t destination) +{ +} + +void BusAccessUnit::systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength) +{ +} + +void BusAccessUnit::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber) +{ +} + +void BusAccessUnit::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber) +{ +} + +void BusAccessUnit::systemNetworkParameterReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength, bool status) +{ +} + +void BusAccessUnit::domainAddressSerialNumberWriteLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber, bool status) +{ +} + +void BusAccessUnit::domainAddressSerialNumberReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber, bool status) +{ +} + +void BusAccessUnit::propertyValueRead(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t** data, uint32_t& length) +{ +} + +void BusAccessUnit::propertyValueWrite(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t* data, uint32_t length) +{ +} + +void BusAccessUnit::beforeRestartCallback(BeforeRestartCallback func) +{ +} + +BeforeRestartCallback BusAccessUnit::beforeRestartCallback() +{ + return 0; +} + +void BusAccessUnit::functionPropertyCallback(FunctionPropertyCallback func) +{ +} + +FunctionPropertyCallback BusAccessUnit::functionPropertyCallback() +{ + return 0; +} + +void BusAccessUnit::functionPropertyStateCallback(FunctionPropertyCallback func) +{ +} + +FunctionPropertyCallback BusAccessUnit::functionPropertyStateCallback() +{ + return 0; +} \ No newline at end of file diff --git a/components/knx/src/knx/bau.h b/components/knx/src/knx/bau.h new file mode 100644 index 0000000..6269abe --- /dev/null +++ b/components/knx/src/knx/bau.h @@ -0,0 +1,184 @@ +#pragma once +#include +#include "knx_types.h" +#include "interface_object.h" + +typedef void (*BeforeRestartCallback)(void); +typedef bool (*FunctionPropertyCallback)(uint8_t objectIndex, uint8_t propertyId, uint8_t length, uint8_t* data, uint8_t* resultData, uint8_t& resultLength); + +class BusAccessUnit +{ + public: + virtual ~BusAccessUnit() {} + virtual void groupValueReadLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, bool status); + virtual void groupValueReadIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl); + virtual void groupValueReadResponseConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopTtype, const SecurityControl& secCtrl, + uint8_t* data, uint8_t dataLength, bool status); + virtual void groupValueReadAppLayerConfirm(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* data, uint8_t dataLength); + virtual void groupValueWriteLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* data, uint8_t dataLength, bool status); + virtual void groupValueWriteIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* data, uint8_t dataLength); + virtual void individualAddressWriteLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, + uint16_t newaddress, bool status); + virtual void individualAddressWriteIndication(HopCountType hopType, const SecurityControl& secCtrl, uint16_t newaddress); + virtual void individualAddressReadLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, bool status); + virtual void individualAddressReadIndication(HopCountType hopType, const SecurityControl& secCtrl); + virtual void individualAddressReadResponseConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, bool status); + virtual void individualAddressReadAppLayerConfirm(HopCountType hopType, const SecurityControl& secCtrl, uint16_t individualAddress); + virtual void individualAddressSerialNumberReadLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* serialNumber, bool status); + virtual void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* knxSerialNumber); + virtual void individualAddressSerialNumberReadResponseConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* serialNumber, uint16_t domainAddress, bool status); + virtual void individualAddressSerialNumberReadAppLayerConfirm(HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, + uint16_t individualAddress, uint16_t domainAddress); + virtual void individualAddressSerialNumberWriteLocalConfirm(AckType ack, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* serialNumber, + uint16_t newaddress, bool status); + virtual void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber); + virtual void deviceDescriptorReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t descriptorType, bool status); + virtual void deviceDescriptorReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t descriptorType); + virtual void deviceDescriptorReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t descriptor_type, uint8_t* device_descriptor, bool status); + virtual void deviceDescriptorReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t descriptortype, uint8_t* deviceDescriptor); + virtual void restartRequestLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, bool status); + virtual void restartRequestIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, RestartType restartType, EraseCode eraseCode, uint8_t channel); + virtual void propertyValueReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, bool status); + virtual void propertyValueReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex); + virtual void propertyValueExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex); + virtual void functionPropertyCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t* data, uint8_t length); + virtual void functionPropertyStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t* data, uint8_t length); + virtual void functionPropertyExtCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t* data, uint8_t length); + virtual void functionPropertyExtStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t* data, uint8_t length); + virtual void propertyValueReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool status); + virtual void propertyValueReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + virtual void propertyValueWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool status); + virtual void propertyValueWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + virtual void propertyValueExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool confirmed); + virtual void propertyDescriptionReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool status); + virtual void propertyExtDescriptionReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectIndex, uint8_t propertyId, uint16_t propertyIndex, bool status); + virtual void propertyDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex); + virtual void propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex); + virtual void propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access); + virtual void propertyDescriptionReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access, bool status); + virtual void propertyDescriptionReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access); + virtual void memoryReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, bool status); + virtual void memoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress); + virtual void memoryReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data, bool status); + virtual void memoryReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + virtual void memoryWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data, bool status); + virtual void memoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + virtual void memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + virtual void memoryRouterReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + virtual void memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress); + virtual void memoryRoutingTableReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data); + virtual void memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data); + virtual void memoryExtReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, bool status); + virtual void memoryExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress); + virtual void memoryExtReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* data, bool status); + virtual void memoryExtReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* data); + virtual void memoryExtWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* data, bool status); + virtual void memoryExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* data); + virtual void memoryExtWriteResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* data, bool status); + virtual void memoryExtWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* data); + virtual void userMemoryReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, bool status); + virtual void userMemoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress); + virtual void userMemoryReadResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData, bool status); + virtual void userMemoryReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData); + virtual void userMemoryWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData, bool status); + virtual void userMemoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData); + virtual void userManufacturerInfoLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, bool status); + virtual void userManufacturerInfoIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl); + virtual void userManufacturerInfoResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t* info, bool status); + virtual void userManufacturerInfoAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint8_t* info); + virtual void authorizeLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key, bool status); + virtual void authorizeIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key); + virtual void authorizeResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, + bool status); + virtual void authorizeAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level); + virtual void keyWriteLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, + uint32_t key, bool status); + virtual void keyWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, + uint32_t key); + virtual void keyWriteResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level, + bool status); + virtual void keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t level); + virtual void connectConfirm(uint16_t destination); + virtual void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength); + + virtual void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber); + + virtual void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber); + + virtual void systemNetworkParameterReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength, bool status); + + virtual void domainAddressSerialNumberWriteLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber, bool status); + + virtual void domainAddressSerialNumberReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber, bool status); + + virtual void propertyValueRead(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t** data, uint32_t& length); + + virtual void propertyValueWrite(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t* data, uint32_t length); + virtual void beforeRestartCallback(BeforeRestartCallback func); + virtual BeforeRestartCallback beforeRestartCallback(); + virtual void functionPropertyCallback(FunctionPropertyCallback func); + virtual FunctionPropertyCallback functionPropertyCallback(); + virtual void functionPropertyStateCallback(FunctionPropertyCallback func); + virtual FunctionPropertyCallback functionPropertyStateCallback(); +}; diff --git a/components/knx/src/knx/bau07B0.cpp b/components/knx/src/knx/bau07B0.cpp new file mode 100644 index 0000000..ce167c0 --- /dev/null +++ b/components/knx/src/knx/bau07B0.cpp @@ -0,0 +1,178 @@ +#include "config.h" +#if MASK_VERSION == 0x07B0 + +#include "bau07B0.h" +#include "bits.h" +#include +#include + +using namespace std; + +Bau07B0::Bau07B0(Platform& platform) + : BauSystemBDevice(platform), DataLinkLayerCallbacks(), + _dlLayer(_deviceObj, _netLayer.getInterface(), _platform, (ITpUartCallBacks&) * this, (DataLinkLayerCallbacks*) this) +#ifdef USE_CEMI_SERVER + , _cemiServer(*this) +#endif +{ + _netLayer.getInterface().dataLinkLayer(_dlLayer); +#ifdef USE_CEMI_SERVER + _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_TP1); + _cemiServer.dataLinkLayer(_dlLayer); + _dlLayer.cemiServer(_cemiServer); + _memory.addSaveRestore(&_cemiServerObject); +#endif + // Set Mask Version in Device Object depending on the BAU + _deviceObj.maskVersion(0x07B0); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + Property* prop = _deviceObj.property(PID_IO_LIST); + prop->write(1, (uint16_t) OT_DEVICE); + prop->write(2, (uint16_t) OT_ADDR_TABLE); + prop->write(3, (uint16_t) OT_ASSOC_TABLE); + prop->write(4, (uint16_t) OT_GRP_OBJ_TABLE); + prop->write(5, (uint16_t) OT_APPLICATION_PROG); +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + prop->write(6, (uint16_t) OT_SECURITY); + prop->write(7, (uint16_t) OT_CEMI_SERVER); +#elif defined(USE_DATASECURE) + prop->write(6, (uint16_t) OT_SECURITY); +#elif defined(USE_CEMI_SERVER) + prop->write(6, (uint16_t) OT_CEMI_SERVER); +#endif +} + +InterfaceObject* Bau07B0::getInterfaceObject(uint8_t idx) +{ + switch (idx) + { + case 0: + return &_deviceObj; + + case 1: + return &_addrTable; + + case 2: + return &_assocTable; + + case 3: + return &_groupObjTable; + + case 4: + return &_appProgram; + + case 5: // would be app_program 2 + return nullptr; +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + + case 6: + return &_secIfObj; + + case 7: + return &_cemiServerObject; +#elif defined(USE_CEMI_SERVER) + + case 6: + return &_cemiServerObject; +#elif defined(USE_DATASECURE) + + case 6: + return &_secIfObj; +#endif + + default: + return nullptr; + } +} + +InterfaceObject* Bau07B0::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) +{ + // We do not use it right now. + // Required for coupler mode as there are multiple router objects for example + (void) objectInstance; + + switch (objectType) + { + case OT_DEVICE: + return &_deviceObj; + + case OT_ADDR_TABLE: + return &_addrTable; + + case OT_ASSOC_TABLE: + return &_assocTable; + + case OT_GRP_OBJ_TABLE: + return &_groupObjTable; + + case OT_APPLICATION_PROG: + return &_appProgram; +#ifdef USE_DATASECURE + + case OT_SECURITY: + return &_secIfObj; +#endif +#ifdef USE_CEMI_SERVER + + case OT_CEMI_SERVER: + return &_cemiServerObject; +#endif + + default: + return nullptr; + } +} + +bool Bau07B0::enabled() +{ + return _dlLayer.enabled(); +} + +void Bau07B0::enabled(bool value) +{ + _dlLayer.enabled(value); +} + +void Bau07B0::loop() +{ + _dlLayer.loop(); + BauSystemBDevice::loop(); +#ifdef USE_CEMI_SERVER + _cemiServer.loop(); +#endif +} + +TPAckType Bau07B0::isAckRequired(uint16_t address, bool isGrpAddr) +{ + if (isGrpAddr) + { + // ACK for broadcasts + if (address == 0) + return TPAckType::AckReqAck; + + // is group address in group address table? ACK if yes. + if (_addrTable.contains(address)) + return TPAckType::AckReqAck; + else + return TPAckType::AckReqNone; + } + + // Also ACK for our own individual address + if (address == _deviceObj.individualAddress()) + return TPAckType::AckReqAck; + + if (address == 0) + { + println("Invalid broadcast detected: destination address is 0, but address type is \"individual\""); + } + + return TPAckType::AckReqNone; +} + +TpUartDataLinkLayer* Bau07B0::getDataLinkLayer() +{ + return (TpUartDataLinkLayer*)&_dlLayer; +} +#endif diff --git a/components/knx/src/knx/bau07B0.h b/components/knx/src/knx/bau07B0.h new file mode 100644 index 0000000..7867d00 --- /dev/null +++ b/components/knx/src/knx/bau07B0.h @@ -0,0 +1,34 @@ +#pragma once + +#include "config.h" +#if MASK_VERSION == 0x07B0 + +#include "bau_systemB_device.h" +#include "tpuart_data_link_layer.h" +#include "cemi_server.h" +#include "cemi_server_object.h" + +class Bau07B0 : public BauSystemBDevice, public ITpUartCallBacks, public DataLinkLayerCallbacks +{ + public: + Bau07B0(Platform& platform); + void loop() override; + bool enabled() override; + void enabled(bool value) override; + + TpUartDataLinkLayer* getDataLinkLayer(); + protected: + InterfaceObject* getInterfaceObject(uint8_t idx); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); + + // For TP1 only + TPAckType isAckRequired(uint16_t address, bool isGrpAddr) override; + + private: + TpUartDataLinkLayer _dlLayer; +#ifdef USE_CEMI_SERVER + CemiServer _cemiServer; + CemiServerObject _cemiServerObject; +#endif +}; +#endif diff --git a/components/knx/src/knx/bau091A.cpp b/components/knx/src/knx/bau091A.cpp new file mode 100644 index 0000000..d468ef7 --- /dev/null +++ b/components/knx/src/knx/bau091A.cpp @@ -0,0 +1,262 @@ +#include "config.h" +#if MASK_VERSION == 0x091A + +#include "bau091A.h" +#include "bits.h" +#include +#include + +using namespace std; + +/* ToDos +Announce the line status of sec side 03_05_01 4.4.3 +implement PID_COUPLER_SERVICES_CONTROL 03_05_01 4.4.7 +*/ + +Bau091A::Bau091A(Platform& platform) + : BauSystemBCoupler(platform), DataLinkLayerCallbacks(), + _routerObj(memory(), 0x200, 0x2000), // the Filtertable of 0x091A IP Routers is fixed at 0x200 and 0x2000 long + _ipParameters(_deviceObj, platform), + _dlLayerPrimary(_deviceObj, _ipParameters, _netLayer.getPrimaryInterface(), _platform, (DataLinkLayerCallbacks*) this), + _dlLayerSecondary(_deviceObj, _netLayer.getSecondaryInterface(), platform, (ITpUartCallBacks&) * this, (DataLinkLayerCallbacks*) this) +#ifdef USE_CEMI_SERVER + , _cemiServer(*this) +#endif +{ + // Before accessing anything of the router object they have to be initialized according to the used medium + // Coupler model 1.x + _routerObj.initialize1x(DptMedium::KNX_IP, 220); + + // Mask 091A uses older coupler model 1.x which only uses one router object + _netLayer.rtObj(_routerObj); + + _netLayer.getPrimaryInterface().dataLinkLayer(_dlLayerPrimary); + _netLayer.getSecondaryInterface().dataLinkLayer(_dlLayerSecondary); + +#ifdef USE_CEMI_SERVER + _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_IP); + _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_TP1); + _cemiServer.dataLinkLayerPrimary(_dlLayerPrimary); + _cemiServer.dataLinkLayer(_dlLayerSecondary); // Secondary I/F is the important one! + _dlLayerPrimary.cemiServer(_cemiServer); + _dlLayerSecondary.cemiServer(_cemiServer); + _memory.addSaveRestore(&_cemiServerObject); + uint8_t count = 1; + uint16_t suppCommModes = 0x0100; + _cemiServerObject.writeProperty(PID_COMM_MODES_SUPPORTED, 1, (uint8_t*)&suppCommModes, count); // set the properties Bit 0 to 1 meaning "LinkLayer supported" +#endif + + _memory.addSaveRestore(&_routerObj); + + _memory.addSaveRestore(&_ipParameters); + + // Set Mask Version in Device Object depending on the BAU + _deviceObj.maskVersion(0x091A); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + Property* prop = _deviceObj.property(PID_IO_LIST); + prop->write(1, (uint16_t) OT_DEVICE); + prop->write(2, (uint16_t) OT_ROUTER); + prop->write(3, (uint16_t) OT_APPLICATION_PROG); + prop->write(4, (uint16_t) OT_IP_PARAMETER); +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + prop->write(5, (uint16_t) OT_SECURITY); + prop->write(6, (uint16_t) OT_CEMI_SERVER); +#elif defined(USE_DATASECURE) + prop->write(5, (uint16_t) OT_SECURITY); +#elif defined(USE_CEMI_SERVER) + prop->write(5, (uint16_t) OT_CEMI_SERVER); +#endif +} + +InterfaceObject* Bau091A::getInterfaceObject(uint8_t idx) +{ + switch (idx) + { + case 0: + return &_deviceObj; + + case 1: + return &_routerObj; + + case 2: + return &_appProgram; + + case 3: + return &_ipParameters; +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + + case 4: + return &_secIfObj; + + case 5: + return &_cemiServerObject; +#elif defined(USE_CEMI_SERVER) + + case 4: + return &_cemiServerObject; +#elif defined(USE_DATASECURE) + + case 4: + return &_secIfObj; +#endif + + default: + return nullptr; + } +} + +InterfaceObject* Bau091A::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) +{ + // We do not use it right now. + // Required for coupler mode as there are multiple router objects for example + (void) objectInstance; + + switch (objectType) + { + case OT_DEVICE: + return &_deviceObj; + + case OT_ROUTER: + return &_routerObj; + + case OT_APPLICATION_PROG: + return &_appProgram; + + case OT_IP_PARAMETER: + return &_ipParameters; +#ifdef USE_DATASECURE + + case OT_SECURITY: + return &_secIfObj; +#endif +#ifdef USE_CEMI_SERVER + + case OT_CEMI_SERVER: + return &_cemiServerObject; +#endif + + default: + return nullptr; + } +} + +void Bau091A::doMasterReset(EraseCode eraseCode, uint8_t channel) +{ + // Common SystemB objects + BauSystemBCoupler::doMasterReset(eraseCode, channel); + + _ipParameters.masterReset(eraseCode, channel); + _routerObj.masterReset(eraseCode, channel); +} + +bool Bau091A::enabled() +{ + return _dlLayerPrimary.enabled() && _dlLayerSecondary.enabled(); +} + +void Bau091A::enabled(bool value) +{ + _dlLayerPrimary.enabled(value); + _dlLayerSecondary.enabled(value); + + // ToDo change frame repitition in the TP layer - but default is ok. + //_dlLayerSecondary.setFrameRepetition(3,3); +} + +void Bau091A::loop() +{ + _dlLayerPrimary.loop(); + _dlLayerSecondary.loop(); + BauSystemBCoupler::loop(); +} + +TPAckType Bau091A::isAckRequired(uint16_t address, bool isGrpAddr) +{ + //only called from TpUartDataLinkLayer + TPAckType ack = TPAckType::AckReqNone; + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig = _routerObj.property(PID_SUB_LCCONFIG); + + if (lcconfig) + prop_lcconfig->read(lcconfig); + + if (isGrpAddr) + { + // ACK for broadcasts + if (address == 0) + { + ack = TPAckType::AckReqAck; + } + else + { + if(lcconfig & LCCONFIG::GROUP_IACK_ROUT) + { + // is group address in filter table? ACK if yes, No if not + if(_netLayer.isRoutedGroupAddress(address, 1)) + ack = TPAckType::AckReqAck; + else + ack = TPAckType::AckReqNone; + } + else + { + // all are ACKED + ack = TPAckType::AckReqAck; + } + } +#ifdef KNX_TUNNELING + + if (_dlLayerPrimary.isSentToTunnel(address, isGrpAddr)) + ack = TPAckType::AckReqAck; + +#endif + } + else + { + if ((lcconfig & LCCONFIG::PHYS_IACK) == LCCONFIG::PHYS_IACK_ALL) + ack = TPAckType::AckReqAck; + else if ((lcconfig & LCCONFIG::PHYS_IACK) == LCCONFIG::PHYS_IACK_NACK) + ack = TPAckType::AckReqNack; + else if (_netLayer.isRoutedIndividualAddress(address, 1) || address == _deviceObj.individualAddress()) // Also ACK for our own individual address + ack = TPAckType::AckReqAck; + else + ack = TPAckType::AckReqNone; + +#ifdef KNX_TUNNELING + + if (_dlLayerPrimary.isSentToTunnel(address, isGrpAddr)) + ack = TPAckType::AckReqAck; + +#endif + } + return ack; +} + +bool Bau091A::configured() +{ + // _configured is set to true initially, if the device was configured with ETS it will be set to true after restart + + if (!_configured) + return false; + + _configured = _routerObj.loadState() == LS_LOADED; +#ifdef USE_DATASECURE + _configured &= _secIfObj.loadState() == LS_LOADED; +#endif + + return _configured; +} + +IpDataLinkLayer* Bau091A::getPrimaryDataLinkLayer() +{ + return (IpDataLinkLayer*)&_dlLayerPrimary; +} + +TpUartDataLinkLayer* Bau091A::getSecondaryDataLinkLayer() +{ + return (TpUartDataLinkLayer*)&_dlLayerSecondary; +} +#endif diff --git a/components/knx/src/knx/bau091A.h b/components/knx/src/knx/bau091A.h new file mode 100644 index 0000000..78f3b43 --- /dev/null +++ b/components/knx/src/knx/bau091A.h @@ -0,0 +1,42 @@ +#pragma once + +#include "config.h" +#if MASK_VERSION == 0x091A + +#include "bau_systemB_coupler.h" +#include "router_object.h" +#include "ip_parameter_object.h" +#include "ip_data_link_layer.h" +#include "tpuart_data_link_layer.h" +#include "cemi_server_object.h" + +class Bau091A : public BauSystemBCoupler, public ITpUartCallBacks, public DataLinkLayerCallbacks +{ + public: + Bau091A(Platform& platform); + void loop() override; + bool enabled() override; + void enabled(bool value) override; + bool configured() override; + + IpDataLinkLayer* getPrimaryDataLinkLayer(); + TpUartDataLinkLayer* getSecondaryDataLinkLayer(); + protected: + InterfaceObject* getInterfaceObject(uint8_t idx); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); + + // For TP1 only + TPAckType isAckRequired(uint16_t address, bool isGrpAddr) override; + + void doMasterReset(EraseCode eraseCode, uint8_t channel) override; + private: + RouterObject _routerObj; + IpParameterObject _ipParameters; + IpDataLinkLayer _dlLayerPrimary; + TpUartDataLinkLayer _dlLayerSecondary; +#ifdef USE_CEMI_SERVER + CemiServer _cemiServer; + CemiServerObject _cemiServerObject; +#endif +}; +#endif diff --git a/components/knx/src/knx/bau27B0.cpp b/components/knx/src/knx/bau27B0.cpp new file mode 100644 index 0000000..c75747b --- /dev/null +++ b/components/knx/src/knx/bau27B0.cpp @@ -0,0 +1,207 @@ +#include "config.h" +#if MASK_VERSION == 0x27B0 + +#include "bau27B0.h" +#include "bits.h" +#include +#include + +using namespace std; + +Bau27B0::Bau27B0(Platform& platform) + : BauSystemBDevice(platform), + _dlLayer(_deviceObj, _rfMediumObj, _netLayer.getInterface(), _platform) +#ifdef USE_CEMI_SERVER + , _cemiServer(*this) +#endif +{ + _netLayer.getInterface().dataLinkLayer(_dlLayer); + _memory.addSaveRestore(&_rfMediumObj); +#ifdef USE_CEMI_SERVER + _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_RF); + _cemiServer.dataLinkLayer(_dlLayer); + _dlLayer.cemiServer(_cemiServer); + _memory.addSaveRestore(&_cemiServerObject); +#endif + + // Set Mask Version in Device Object depending on the BAU + _deviceObj.maskVersion(0x27B0); + + // Set the maximum APDU length + // ETS will consider this value while programming the device + // For KNX-RF we use a smallest allowed value for now, + // although long frame are also supported by the implementation. + // Needs some experimentation. + _deviceObj.maxApduLength(15); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + Property* prop = _deviceObj.property(PID_IO_LIST); + prop->write(1, (uint16_t) OT_DEVICE); + prop->write(2, (uint16_t) OT_ADDR_TABLE); + prop->write(3, (uint16_t) OT_ASSOC_TABLE); + prop->write(4, (uint16_t) OT_GRP_OBJ_TABLE); + prop->write(5, (uint16_t) OT_APPLICATION_PROG); + prop->write(6, (uint16_t) OT_RF_MEDIUM); +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + prop->write(7, (uint16_t) OT_SECURITY); + prop->write(8, (uint16_t) OT_CEMI_SERVER); +#elif defined(USE_DATASECURE) + prop->write(7, (uint16_t) OT_SECURITY); +#elif defined(USE_CEMI_SERVER) + prop->write(7, (uint16_t)OT_CEMI_SERVER); +#endif +} + +// see KNX AN160 p.74 for mask 27B0 +InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx) +{ + switch (idx) + { + case 0: + return &_deviceObj; + + case 1: + return &_addrTable; + + case 2: + return &_assocTable; + + case 3: + return &_groupObjTable; + + case 4: + return &_appProgram; + + case 5: // would be app_program 2 + return nullptr; + + case 6: + return &_rfMediumObj; +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + + case 7: + return &_secIfObj; + + case 8: + return &_cemiServerObject; +#elif defined(USE_CEMI_SERVER) + + case 7: + return &_cemiServerObject; +#elif defined(USE_DATASECURE) + + case 7: + return &_secIfObj; +#endif + + default: + return nullptr; + } +} + +InterfaceObject* Bau27B0::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) +{ + // We do not use it right now. + // Required for coupler mode as there are multiple router objects for example + (void) objectInstance; + + switch (objectType) + { + case OT_DEVICE: + return &_deviceObj; + + case OT_ADDR_TABLE: + return &_addrTable; + + case OT_ASSOC_TABLE: + return &_assocTable; + + case OT_GRP_OBJ_TABLE: + return &_groupObjTable; + + case OT_APPLICATION_PROG: + return &_appProgram; + + case OT_RF_MEDIUM: + return &_rfMediumObj; +#ifdef USE_DATASECURE + + case OT_SECURITY: + return &_secIfObj; +#endif +#ifdef USE_CEMI_SERVER + + case OT_CEMI_SERVER: + return &_cemiServerObject; +#endif + + default: + return nullptr; + } +} + +void Bau27B0::doMasterReset(EraseCode eraseCode, uint8_t channel) +{ + // Common SystemB objects + BauSystemB::doMasterReset(eraseCode, channel); + + _rfMediumObj.masterReset(eraseCode, channel); +} + +bool Bau27B0::enabled() +{ + return _dlLayer.enabled(); +} + +void Bau27B0::enabled(bool value) +{ + _dlLayer.enabled(value); +} + +void Bau27B0::loop() +{ + _dlLayer.loop(); + BauSystemBDevice::loop(); +#ifdef USE_CEMI_SERVER + _cemiServer.loop(); +#endif +} + +void Bau27B0::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber) +{ + // If the received serial number matches our serial number + // then store the received RF domain address in the RF medium object + if (!memcmp(knxSerialNumber, _deviceObj.propertyData(PID_SERIAL_NUMBER), 6)) + _rfMediumObj.rfDomainAddress(rfDoA); +} + +void Bau27B0::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber) +{ + // If the received serial number matches our serial number + // then send a response with the current RF domain address stored in the RF medium object + if (!memcmp(knxSerialNumber, _deviceObj.propertyData(PID_SERIAL_NUMBER), 6)) + _appLayer.domainAddressSerialNumberReadResponse(priority, hopType, secCtrl, _rfMediumObj.rfDomainAddress(), knxSerialNumber); +} + +void Bau27B0::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* knxSerialNumber) +{ +#pragma warning "individualAddressSerialNumberReadIndication is not available for rf" +} + +void Bau27B0::domainAddressSerialNumberWriteLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber, bool status) +{ +} + +void Bau27B0::domainAddressSerialNumberReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber, bool status) +{ +} + +RfDataLinkLayer* Bau27B0::getDataLinkLayer() +{ + return (RfDataLinkLayer*)&_dlLayer; +} +#endif // #ifdef USE_RF diff --git a/components/knx/src/knx/bau27B0.h b/components/knx/src/knx/bau27B0.h new file mode 100644 index 0000000..3b5e423 --- /dev/null +++ b/components/knx/src/knx/bau27B0.h @@ -0,0 +1,47 @@ +#pragma once + +#include "config.h" +#if MASK_VERSION == 0x27B0 + +#include "bau_systemB_device.h" +#include "rf_medium_object.h" +#if defined(DeviceFamily_CC13X0) + #include "rf_physical_layer_cc1310.h" +#else + #include "rf_physical_layer_cc1101.h" +#endif +#include "rf_data_link_layer.h" +#include "cemi_server.h" +#include "cemi_server_object.h" + +class Bau27B0 : public BauSystemBDevice +{ + public: + Bau27B0(Platform& platform); + void loop() override; + bool enabled() override; + void enabled(bool value) override; + + RfDataLinkLayer* getDataLinkLayer(); + protected: + InterfaceObject* getInterfaceObject(uint8_t idx); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); + + void doMasterReset(EraseCode eraseCode, uint8_t channel) override; + private: + RfDataLinkLayer _dlLayer; + RfMediumObject _rfMediumObj; +#ifdef USE_CEMI_SERVER + CemiServer _cemiServer; + CemiServerObject _cemiServerObject; +#endif + + void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber) override; + void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber) override; + void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* knxSerialNumber) override; + void domainAddressSerialNumberWriteLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* rfDoA, + const uint8_t* knxSerialNumber, bool status) override; + void domainAddressSerialNumberReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, const uint8_t* knxSerialNumber, bool status) override; +}; +#endif diff --git a/components/knx/src/knx/bau2920.cpp b/components/knx/src/knx/bau2920.cpp new file mode 100644 index 0000000..28e96f5 --- /dev/null +++ b/components/knx/src/knx/bau2920.cpp @@ -0,0 +1,181 @@ +#include "config.h" +#if MASK_VERSION == 0x2920 + +#include "bau2920.h" +#include "bits.h" +#include +#include + +using namespace std; + +// Mask 0x2920 uses coupler model 2.0 +Bau2920::Bau2920(Platform& platform) + : BauSystemBCoupler(platform), + _rtObjPrimary(memory()), + _rtObjSecondary(memory()), + _rfMediumObject(), + _dlLayerPrimary(_deviceObj, _netLayer.getPrimaryInterface(), _platform, (ITpUartCallBacks&) * this), + _dlLayerSecondary(_deviceObj, _rfMediumObject, _netLayer.getSecondaryInterface(), platform) +#ifdef USE_CEMI_SERVER + , + _cemiServer(*this) +#endif +{ + // Before accessing anything of the two router objects they have to be initialized according to the used media combination + // Coupler model 2.0 + _rtObjPrimary.initialize20(1, DptMedium::KNX_TP1, RouterObjectType::Primary, 201); + _rtObjSecondary.initialize20(2, DptMedium::KNX_RF, RouterObjectType::Secondary, 201); + + _netLayer.rtObjPrimary(_rtObjPrimary); + _netLayer.rtObjSecondary(_rtObjSecondary); + _netLayer.getPrimaryInterface().dataLinkLayer(_dlLayerPrimary); + _netLayer.getSecondaryInterface().dataLinkLayer(_dlLayerSecondary); + +#ifdef USE_CEMI_SERVER + _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_TP1); + _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_RF); + _cemiServer.dataLinkLayer(_dlLayerSecondary); // Secondary I/F is the important one! + _dlLayerSecondary.cemiServer(_cemiServer); + _memory.addSaveRestore(&_cemiServerObject); +#endif + + _memory.addSaveRestore(&_rtObjPrimary); + _memory.addSaveRestore(&_rtObjSecondary); + + _memory.addSaveRestore(&_rfMediumObject); + + // Set Mask Version in Device Object depending on the BAU + _deviceObj.maskVersion(0x2920); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + Property* prop = _deviceObj.property(PID_IO_LIST); + prop->write(1, (uint16_t) OT_DEVICE); + prop->write(2, (uint16_t) OT_ROUTER); + prop->write(3, (uint16_t) OT_ROUTER); + prop->write(4, (uint16_t) OT_APPLICATION_PROG); + prop->write(5, (uint16_t) OT_RF_MEDIUM); +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + prop->write(6, (uint16_t) OT_SECURITY); + prop->write(7, (uint16_t) OT_CEMI_SERVER); +#elif defined(USE_DATASECURE) + prop->write(6, (uint16_t) OT_SECURITY); +#elif defined(USE_CEMI_SERVER) + prop->write(6, (uint16_t) OT_CEMI_SERVER); +#endif +} + +InterfaceObject* Bau2920::getInterfaceObject(uint8_t idx) +{ + switch (idx) + { + case 0: + return &_deviceObj; + + case 1: + return &_rtObjPrimary; + + case 2: + return &_rtObjSecondary; + + case 3: + return &_appProgram; + + case 4: + return &_rfMediumObject; +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + + case 5: + return &_secIfObj; + + case 6: + return &_cemiServerObject; +#elif defined(USE_CEMI_SERVER) + + case 5: + return &_cemiServerObject; +#elif defined(USE_DATASECURE) + + case 5: + return &_secIfObj; +#endif + + default: + return nullptr; + } +} + +InterfaceObject* Bau2920::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) +{ + // We do not use it right now. + // Required for coupler mode as there are multiple router objects for example + (void) objectInstance; + + switch (objectType) + { + case OT_DEVICE: + return &_deviceObj; + + case OT_ROUTER: + return objectInstance == 0 ? &_rtObjPrimary : &_rtObjSecondary; + + case OT_APPLICATION_PROG: + return &_appProgram; + + case OT_RF_MEDIUM: + return &_rfMediumObject; +#ifdef USE_DATASECURE + + case OT_SECURITY: + return &_secIfObj; +#endif +#ifdef USE_CEMI_SERVER + + case OT_CEMI_SERVER: + return &_cemiServerObject; +#endif + + default: + return nullptr; + } +} + +void Bau2920::doMasterReset(EraseCode eraseCode, uint8_t channel) +{ + // Common SystemB objects + BauSystemBCoupler::doMasterReset(eraseCode, channel); + + _rfMediumObject.masterReset(eraseCode, channel); + _rtObjPrimary.masterReset(eraseCode, channel); + _rtObjSecondary.masterReset(eraseCode, channel); +} + +bool Bau2920::enabled() +{ + return _dlLayerPrimary.enabled() && _dlLayerSecondary.enabled(); +} + +void Bau2920::enabled(bool value) +{ + _dlLayerPrimary.enabled(value); + _dlLayerSecondary.enabled(value); +} + +void Bau2920::loop() +{ + _dlLayerPrimary.loop(); + _dlLayerSecondary.loop(); + BauSystemBCoupler::loop(); +} + +TpUartDataLinkLayer* Bau2920::getPrimaryDataLinkLayer() +{ + return (TpUartDataLinkLayer*)&_dlLayerPrimary; +} + +RfDataLinkLayer* Bau2920::getSecondaryDataLinkLayer() +{ + return (RfDataLinkLayer*)&_dlLayerSecondary; +} +#endif diff --git a/components/knx/src/knx/bau2920.h b/components/knx/src/knx/bau2920.h new file mode 100644 index 0000000..210f4af --- /dev/null +++ b/components/knx/src/knx/bau2920.h @@ -0,0 +1,43 @@ +#pragma once + +#include "config.h" +#if MASK_VERSION == 0x2920 + +#include "bau_systemB_coupler.h" +#include "tpuart_data_link_layer.h" +#if defined(DeviceFamily_CC13X0) + #include "rf_physical_layer_cc1310.h" +#else + #include "rf_physical_layer_cc1101.h" +#endif +#include "rf_data_link_layer.h" +#include "rf_medium_object.h" +#include "cemi_server_object.h" + +class Bau2920 : public BauSystemBCoupler +{ + public: + Bau2920(Platform& platform); + void loop() override; + bool enabled() override; + void enabled(bool value) override; + + TpUartDataLinkLayer* getPrimaryDataLinkLayer(); + RfDataLinkLayer* getSecondaryDataLinkLayer(); + protected: + InterfaceObject* getInterfaceObject(uint8_t idx); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); + + void doMasterReset(EraseCode eraseCode, uint8_t channel) override; + private: + RouterObject _rtObjPrimary; + RouterObject _rtObjSecondary; + RfMediumObject _rfMediumObject; + TpUartDataLinkLayer _dlLayerPrimary; + RfDataLinkLayer _dlLayerSecondary; +#ifdef USE_CEMI_SERVER + CemiServer _cemiServer; + CemiServerObject _cemiServerObject; +#endif +}; +#endif diff --git a/components/knx/src/knx/bau57B0.cpp b/components/knx/src/knx/bau57B0.cpp new file mode 100644 index 0000000..32944e0 --- /dev/null +++ b/components/knx/src/knx/bau57B0.cpp @@ -0,0 +1,169 @@ +#include "config.h" +#if MASK_VERSION == 0x57B0 + +#include "bau57B0.h" +#include "bits.h" +#include +#include + +using namespace std; + +Bau57B0::Bau57B0(Platform& platform) + : BauSystemBDevice(platform), DataLinkLayerCallbacks(), + _ipParameters(_deviceObj, platform), + _dlLayer(_deviceObj, _ipParameters, _netLayer.getInterface(), _platform, (DataLinkLayerCallbacks*) this) +#ifdef USE_CEMI_SERVER + , _cemiServer(*this) +#endif +{ + _netLayer.getInterface().dataLinkLayer(_dlLayer); +#ifdef USE_CEMI_SERVER + _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_IP); + _cemiServer.dataLinkLayer(_dlLayer); + _dlLayer.cemiServer(_cemiServer); + _memory.addSaveRestore(&_cemiServerObject); +#endif + _memory.addSaveRestore(&_ipParameters); + + // Set Mask Version in Device Object depending on the BAU + _deviceObj.maskVersion(0x57B0); + + // Set which interface objects are available in the device object + // This differs from BAU to BAU with different medium types. + // See PID_IO_LIST + Property* prop = _deviceObj.property(PID_IO_LIST); + prop->write(1, (uint16_t) OT_DEVICE); + prop->write(2, (uint16_t) OT_ADDR_TABLE); + prop->write(3, (uint16_t) OT_ASSOC_TABLE); + prop->write(4, (uint16_t) OT_GRP_OBJ_TABLE); + prop->write(5, (uint16_t) OT_APPLICATION_PROG); + prop->write(6, (uint16_t) OT_IP_PARAMETER); +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + prop->write(7, (uint16_t) OT_SECURITY); + prop->write(8, (uint16_t) OT_CEMI_SERVER); +#elif defined(USE_DATASECURE) + prop->write(7, (uint16_t) OT_SECURITY); +#elif defined(USE_CEMI_SERVER) + prop->write(7, (uint16_t) OT_CEMI_SERVER); +#endif +} + +InterfaceObject* Bau57B0::getInterfaceObject(uint8_t idx) +{ + switch (idx) + { + case 0: + return &_deviceObj; + + case 1: + return &_addrTable; + + case 2: + return &_assocTable; + + case 3: + return &_groupObjTable; + + case 4: + return &_appProgram; + + case 5: // would be app_program 2 + return nullptr; + + case 6: + return &_ipParameters; +#if defined(USE_DATASECURE) && defined(USE_CEMI_SERVER) + + case 7: + return &_secIfObj; + + case 8: + return &_cemiServerObject; +#elif defined(USE_CEMI_SERVER) + + case 7: + return &_cemiServerObject; +#elif defined(USE_DATASECURE) + + case 7: + return &_secIfObj; +#endif + + default: + return nullptr; + } +} + +InterfaceObject* Bau57B0::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) +{ + // We do not use it right now. + // Required for coupler mode as there are multiple router objects for example + (void) objectInstance; + + switch (objectType) + { + case OT_DEVICE: + return &_deviceObj; + + case OT_ADDR_TABLE: + return &_addrTable; + + case OT_ASSOC_TABLE: + return &_assocTable; + + case OT_GRP_OBJ_TABLE: + return &_groupObjTable; + + case OT_APPLICATION_PROG: + return &_appProgram; + + case OT_IP_PARAMETER: + return &_ipParameters; +#ifdef USE_DATASECURE + + case OT_SECURITY: + return &_secIfObj; +#endif +#ifdef USE_CEMI_SERVER + + case OT_CEMI_SERVER: + return &_cemiServerObject; +#endif + + default: + return nullptr; + } +} + +void Bau57B0::doMasterReset(EraseCode eraseCode, uint8_t channel) +{ + // Common SystemB objects + BauSystemB::doMasterReset(eraseCode, channel); + + _ipParameters.masterReset(eraseCode, channel); +} + +bool Bau57B0::enabled() +{ + return _dlLayer.enabled(); +} + +void Bau57B0::enabled(bool value) +{ + _dlLayer.enabled(value); +} + +void Bau57B0::loop() +{ + _dlLayer.loop(); + BauSystemBDevice::loop(); +#ifdef USE_CEMI_SERVER + _cemiServer.loop(); +#endif +} + +IpDataLinkLayer* Bau57B0::getDataLinkLayer() +{ + return (IpDataLinkLayer*)&_dlLayer; +} +#endif diff --git a/components/knx/src/knx/bau57B0.h b/components/knx/src/knx/bau57B0.h new file mode 100644 index 0000000..48ec370 --- /dev/null +++ b/components/knx/src/knx/bau57B0.h @@ -0,0 +1,33 @@ +#pragma once + +#include "config.h" +#if MASK_VERSION == 0x57B0 + +#include "bau_systemB_device.h" +#include "ip_parameter_object.h" +#include "ip_data_link_layer.h" +#include "cemi_server_object.h" + +class Bau57B0 : public BauSystemBDevice, public DataLinkLayerCallbacks +{ + public: + Bau57B0(Platform& platform); + void loop() override; + bool enabled() override; + void enabled(bool value) override; + + IpDataLinkLayer* getDataLinkLayer(); + protected: + InterfaceObject* getInterfaceObject(uint8_t idx); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); + + void doMasterReset(EraseCode eraseCode, uint8_t channel) override; + private: + IpParameterObject _ipParameters; + IpDataLinkLayer _dlLayer; +#ifdef USE_CEMI_SERVER + CemiServer _cemiServer; + CemiServerObject _cemiServerObject; +#endif +}; +#endif diff --git a/components/knx/src/knx/bau_systemB.cpp b/components/knx/src/knx/bau_systemB.cpp new file mode 100644 index 0000000..b727148 --- /dev/null +++ b/components/knx/src/knx/bau_systemB.cpp @@ -0,0 +1,812 @@ +#include "bau_systemB.h" +#include "bits.h" +#include +#include + +enum NmReadSerialNumberType +{ + NM_Read_SerialNumber_By_ProgrammingMode = 0x01, + NM_Read_SerialNumber_By_ExFactoryState = 0x02, + NM_Read_SerialNumber_By_PowerReset = 0x03, + NM_Read_SerialNumber_By_ManufacturerSpecific = 0xFE, +}; + +static constexpr auto kFunctionPropertyResultBufferMaxSize = 0xFF; +static constexpr auto kRestartProcessTime = 3; + +BauSystemB::BauSystemB(Platform& platform): _memory(platform, _deviceObj), + _appProgram(_memory), + _platform(platform) +{ + _memory.addSaveRestore(&_appProgram); +} + +void BauSystemB::readMemory() +{ + _memory.readMemory(); +} + +void BauSystemB::writeMemory() +{ + _memory.writeMemory(); +} + +Platform& BauSystemB::platform() +{ + return _platform; +} + +ApplicationProgramObject& BauSystemB::parameters() +{ + return _appProgram; +} + +DeviceObject& BauSystemB::deviceObject() +{ + return _deviceObj; +} + +uint8_t BauSystemB::checkmasterResetValidity(EraseCode eraseCode, uint8_t channel) +{ + static constexpr uint8_t successCode = 0x00; // Where does this come from? It is the code for "success". + static constexpr uint8_t invalidEraseCode = 0x02; // Where does this come from? It is the error code for "unspported erase code". + + switch (eraseCode) + { + case EraseCode::ConfirmedRestart: + { + println("Confirmed restart requested."); + return successCode; + } + + case EraseCode::ResetAP: + { + // TODO: increase download counter except for confirmed restart (PID_DOWNLOAD_COUNTER) + println("ResetAP requested. Not implemented yet."); + return successCode; + } + + case EraseCode::ResetIA: + { + // TODO: increase download counter except for confirmed restart (PID_DOWNLOAD_COUNTER) + println("ResetIA requested. Not implemented yet."); + return successCode; + } + + case EraseCode::ResetLinks: + { + // TODO: increase download counter except for confirmed restart (PID_DOWNLOAD_COUNTER) + println("ResetLinks requested. Not implemented yet."); + return successCode; + } + + case EraseCode::ResetParam: + { + // TODO: increase download counter except for confirmed restart (PID_DOWNLOAD_COUNTER) + println("ResetParam requested. Not implemented yet."); + return successCode; + } + + case EraseCode::FactoryReset: + { + // TODO: increase download counter except for confirmed restart (PID_DOWNLOAD_COUNTER) + println("Factory reset requested. type: with IA"); + return successCode; + } + + case EraseCode::FactoryResetWithoutIA: + { + // TODO: increase download counter except for confirmed restart (PID_DOWNLOAD_COUNTER) + println("Factory reset requested. type: without IA"); + return successCode; + } + + default: + { + print("Unhandled erase code: "); + println(eraseCode, HEX); + return invalidEraseCode; + } + } +} + +void BauSystemB::deviceDescriptorReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t descriptorType) +{ + if (descriptorType != 0) + descriptorType = 0x3f; + + uint8_t data[2]; + pushWord(_deviceObj.maskVersion(), data); + applicationLayer().deviceDescriptorReadResponse(AckRequested, priority, hopType, asap, secCtrl, descriptorType, data); +} +void BauSystemB::memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data) +{ + print("Writing memory at: "); + print(memoryAddress, HEX); + print(" length: "); + print(number); + print(" data: "); + printHex("=>", data, number); + _memory.writeMemory(memoryAddress, number, data); + + if (_deviceObj.verifyMode()) + { + print("Sending Read indication"); + memoryRouterReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress, data); + } +} + +void BauSystemB::memoryRouterReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data) +{ + applicationLayer().memoryRouterReadResponse(AckRequested, priority, hopType, asap, secCtrl, number, memoryAddress, data); +} + +void BauSystemB::memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ + applicationLayer().memoryRoutingTableReadResponse(AckRequested, priority, hopType, asap, secCtrl, number, memoryAddress, data); +} +void BauSystemB::memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress) +{ + memoryRoutingTableReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress, _memory.toAbsolute(memoryAddress)); +} + +void BauSystemB::memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ + print("Writing memory at: "); + print(memoryAddress, HEX); + print(" length: "); + print(number); + print(" data: "); + printHex("=>", data, number); + _memory.writeMemory(memoryAddress, number, data); + + if (_deviceObj.verifyMode()) + memoryRoutingTableReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress, data); +} + +void BauSystemB::memoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data) +{ + _memory.writeMemory(memoryAddress, number, data); + + if (_deviceObj.verifyMode()) + memoryReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress, data); +} + +void BauSystemB::memoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data) +{ + applicationLayer().memoryReadResponse(AckRequested, priority, hopType, asap, secCtrl, number, memoryAddress, data); +} + +void BauSystemB::memoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress) +{ + applicationLayer().memoryReadResponse(AckRequested, priority, hopType, asap, secCtrl, number, memoryAddress, + _memory.toAbsolute(memoryAddress)); +} + +void BauSystemB::memoryExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data) +{ + _memory.writeMemory(memoryAddress, number, data); + + applicationLayer().memoryExtWriteResponse(AckRequested, priority, hopType, asap, secCtrl, ReturnCodes::Success, number, memoryAddress, _memory.toAbsolute(memoryAddress)); +} + +void BauSystemB::memoryExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress) +{ + applicationLayer().memoryExtReadResponse(AckRequested, priority, hopType, asap, secCtrl, ReturnCodes::Success, number, memoryAddress, _memory.toAbsolute(memoryAddress)); +} + +void BauSystemB::doMasterReset(EraseCode eraseCode, uint8_t channel) +{ + _deviceObj.masterReset(eraseCode, channel); + _appProgram.masterReset(eraseCode, channel); +} + +void BauSystemB::restartRequestIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, RestartType restartType, EraseCode eraseCode, uint8_t channel) +{ + if (restartType == RestartType::BasicRestart) + { + println("Basic restart requested"); + + if (_beforeRestart != 0) + _beforeRestart(); + } + else if (restartType == RestartType::MasterReset) + { + uint8_t errorCode = checkmasterResetValidity(eraseCode, channel); + // We send the restart response now before actually applying the reset values + // Processing time is kRestartProcessTime (example 3 seconds) that we require for the applying the master reset with restart + applicationLayer().restartResponse(AckRequested, priority, hopType, secCtrl, errorCode, (errorCode == 0) ? kRestartProcessTime : 0); + doMasterReset(eraseCode, channel); + } + else + { + // Cannot happen as restartType is just one bit + println("Unhandled restart type."); + _platform.fatalError(); + } + + // Flush the EEPROM before resetting + _memory.writeMemory(); + _platform.restart(); +} + +void BauSystemB::authorizeIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key) +{ + applicationLayer().authorizeResponse(AckRequested, priority, hopType, asap, secCtrl, 0); +} + +void BauSystemB::userMemoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress) +{ + applicationLayer().userMemoryReadResponse(AckRequested, priority, hopType, asap, secCtrl, number, memoryAddress, + _memory.toAbsolute(memoryAddress)); +} + +void BauSystemB::userMemoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data) +{ + _memory.writeMemory(memoryAddress, number, data); + + if (_deviceObj.verifyMode()) + userMemoryReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress); +} + +void BauSystemB::propertyDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t propertyIndex) +{ + uint8_t pid = propertyId; + bool writeEnable = false; + uint8_t type = 0; + uint16_t numberOfElements = 0; + uint8_t access = 0; + InterfaceObject* obj = getInterfaceObject(objectIndex); + + if (obj) + obj->readPropertyDescription(pid, propertyIndex, writeEnable, type, numberOfElements, access); + + applicationLayer().propertyDescriptionReadResponse(AckRequested, priority, hopType, asap, secCtrl, objectIndex, pid, propertyIndex, + writeEnable, type, numberOfElements, access); +} + +void BauSystemB::propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex) +{ + uint8_t pid = propertyId; + uint8_t pidx = propertyIndex; + + if (propertyId > 0xFF || propertyIndex > 0xFF) + { + println("BauSystemB::propertyExtDescriptionReadIndication: propertyId or Idx > 256 are not supported"); + return; + } + + if (descriptionType != 0) + { + println("BauSystemB::propertyExtDescriptionReadIndication: only descriptionType 0 supported"); + return; + } + + bool writeEnable = false; + uint8_t type = 0; + uint16_t numberOfElements = 0; + uint8_t access = 0; + InterfaceObject* obj = getInterfaceObject((ObjectType)objectType, objectInstance); + + if (obj) + obj->readPropertyDescription(pid, pidx, writeEnable, type, numberOfElements, access); + + applicationLayer().propertyExtDescriptionReadResponse(AckRequested, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, propertyIndex, + descriptionType, writeEnable, type, numberOfElements, access); +} + +void BauSystemB::propertyValueWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + InterfaceObject* obj = getInterfaceObject(objectIndex); + + if (obj) + obj->writeProperty((PropertyID)propertyId, startIndex, data, numberOfElements); + + propertyValueReadIndication(priority, hopType, asap, secCtrl, objectIndex, propertyId, numberOfElements, startIndex); +} + +void BauSystemB::propertyValueExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool confirmed) +{ + uint8_t returnCode = ReturnCodes::Success; + + InterfaceObject* obj = getInterfaceObject(objectType, objectInstance); + + if (obj) + obj->writeProperty((PropertyID)propertyId, startIndex, data, numberOfElements); + else + returnCode = ReturnCodes::AddressVoid; + + if (confirmed) + { + applicationLayer().propertyValueExtWriteConResponse(AckRequested, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, numberOfElements, startIndex, returnCode); + } +} + +void BauSystemB::propertyValueReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) +{ + uint8_t size = 0; + uint8_t elementCount = numberOfElements; +#ifdef LOG_KNX_PROP + print("propertyValueReadIndication: ObjIdx "); + print(objectIndex); + print(" propId "); + print(propertyId); + print(" num "); + print(numberOfElements); + print(" start "); + print(startIndex); +#endif + + InterfaceObject* obj = getInterfaceObject(objectIndex); + + if (obj) + { + uint8_t elementSize = obj->propertySize((PropertyID)propertyId); + + if (startIndex > 0) + size = elementSize * numberOfElements; + else + size = sizeof(uint16_t); // size of property array entry 0 which contains the current number of elements + } + else + elementCount = 0; + + uint8_t data[size]; + + if (obj) + obj->readProperty((PropertyID)propertyId, startIndex, elementCount, data); + + if (elementCount == 0) + size = 0; + + applicationLayer().propertyValueReadResponse(AckRequested, priority, hopType, asap, secCtrl, objectIndex, propertyId, elementCount, + startIndex, data, size); +} + +void BauSystemB::propertyValueExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) +{ + uint8_t size = 0; + uint8_t elementCount = numberOfElements; + InterfaceObject* obj = getInterfaceObject(objectType, objectInstance); + + if (obj) + { + uint8_t elementSize = obj->propertySize((PropertyID)propertyId); + + if (startIndex > 0) + size = elementSize * numberOfElements; + else + size = sizeof(uint16_t); // size of propert array entry 0 which is the size + } + else + elementCount = 0; + + uint8_t data[size]; + + if (obj) + obj->readProperty((PropertyID)propertyId, startIndex, elementCount, data); + + if (elementCount == 0) + size = 0; + + applicationLayer().propertyValueExtReadResponse(AckRequested, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, elementCount, + startIndex, data, size); +} + +void BauSystemB::functionPropertyCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t* data, uint8_t length) +{ + uint8_t resultData[kFunctionPropertyResultBufferMaxSize]; + uint8_t resultLength = sizeof(resultData); // tell the callee the maximum size of the buffer + + bool handled = false; + + InterfaceObject* obj = getInterfaceObject(objectIndex); + + if (obj) + { + if (obj->property((PropertyID)propertyId)->Type() == PDT_FUNCTION) + { + obj->command((PropertyID)propertyId, data, length, resultData, resultLength); + handled = true; + } + else + { + if (_functionProperty != 0) + if (_functionProperty(objectIndex, propertyId, length, data, resultData, resultLength)) + handled = true; + } + } + else + { + if (_functionProperty != 0) + if (_functionProperty(objectIndex, propertyId, length, data, resultData, resultLength)) + handled = true; + } + + //only return a value it was handled by a property or function + if (handled) + applicationLayer().functionPropertyStateResponse(AckRequested, priority, hopType, asap, secCtrl, objectIndex, propertyId, resultData, resultLength); +} + +void BauSystemB::functionPropertyStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t* data, uint8_t length) +{ + uint8_t resultData[kFunctionPropertyResultBufferMaxSize]; + uint8_t resultLength = sizeof(resultData); // tell the callee the maximum size of the buffer + + bool handled = true; + + InterfaceObject* obj = getInterfaceObject(objectIndex); + + if (obj) + { + if (obj->property((PropertyID)propertyId)->Type() == PDT_FUNCTION) + { + obj->state((PropertyID)propertyId, data, length, resultData, resultLength); + handled = true; + } + else + { + if (_functionPropertyState != 0) + if (_functionPropertyState(objectIndex, propertyId, length, data, resultData, resultLength)) + handled = true; + } + } + else + { + if (_functionPropertyState != 0) + if (_functionPropertyState(objectIndex, propertyId, length, data, resultData, resultLength)) + handled = true; + } + + //only return a value it was handled by a property or function + if (handled) + applicationLayer().functionPropertyStateResponse(AckRequested, priority, hopType, asap, secCtrl, objectIndex, propertyId, resultData, resultLength); +} + +void BauSystemB::functionPropertyExtCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t* data, uint8_t length) +{ + uint8_t resultData[kFunctionPropertyResultBufferMaxSize]; + uint8_t resultLength = 1; // we always have to include the return code at least + + InterfaceObject* obj = getInterfaceObject(objectType, objectInstance); + + if (obj) + { + PropertyDataType propType = obj->property((PropertyID)propertyId)->Type(); + + if (propType == PDT_FUNCTION) + { + // The first byte is reserved and 0 for PDT_FUNCTION + uint8_t reservedByte = data[0]; + + if (reservedByte != 0x00) + { + resultData[0] = ReturnCodes::DataVoid; + } + else + { + resultLength = sizeof(resultData); // tell the callee the maximum size of the buffer + obj->command((PropertyID)propertyId, data, length, resultData, resultLength); + // resultLength was modified by the callee + } + } + else if (propType == PDT_CONTROL) + { + uint8_t count = 1; + // write the event + obj->writeProperty((PropertyID)propertyId, 1, data, count); + + if (count == 1) + { + // Read the current state (one byte only) for the response + obj->readProperty((PropertyID)propertyId, 1, count, &resultData[1]); + resultLength = count ? 2 : 1; + resultData[0] = count ? ReturnCodes::Success : ReturnCodes::DataVoid; + } + else + { + resultData[0] = ReturnCodes::AddressVoid; + } + } + else + { + resultData[0] = ReturnCodes::DataTypeConflict; + } + } + else + { + resultData[0] = ReturnCodes::GenericError; + } + + applicationLayer().functionPropertyExtStateResponse(AckRequested, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, resultData, resultLength); +} + +void BauSystemB::functionPropertyExtStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t* data, uint8_t length) +{ + uint8_t resultData[kFunctionPropertyResultBufferMaxSize]; + uint8_t resultLength = sizeof(resultData); // tell the callee the maximum size of the buffer + + InterfaceObject* obj = getInterfaceObject(objectType, objectInstance); + + if (obj) + { + PropertyDataType propType = obj->property((PropertyID)propertyId)->Type(); + + if (propType == PDT_FUNCTION) + { + // The first byte is reserved and 0 for PDT_FUNCTION + uint8_t reservedByte = data[0]; + + if (reservedByte != 0x00) + { + resultData[0] = ReturnCodes::DataVoid; + } + else + { + resultLength = sizeof(resultData); // tell the callee the maximum size of the buffer + obj->state((PropertyID)propertyId, data, length, resultData, resultLength); + // resultLength was modified by the callee + } + } + else if (propType == PDT_CONTROL) + { + uint8_t count = 1; + // Read the current state (one byte only) for the response + obj->readProperty((PropertyID)propertyId, 1, count, &resultData[1]); + resultLength = count ? 2 : 1; + resultData[0] = count ? ReturnCodes::Success : ReturnCodes::DataVoid; + } + else + { + resultData[0] = ReturnCodes::DataTypeConflict; + } + } + else + { + resultData[0] = ReturnCodes::GenericError; + } + + applicationLayer().functionPropertyExtStateResponse(AckRequested, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, resultData, resultLength); +} + +void BauSystemB::individualAddressReadIndication(HopCountType hopType, const SecurityControl& secCtrl) +{ + if (_deviceObj.progMode()) + applicationLayer().individualAddressReadResponse(AckRequested, hopType, secCtrl); +} + +void BauSystemB::individualAddressWriteIndication(HopCountType hopType, const SecurityControl& secCtrl, uint16_t newaddress) +{ + if (_deviceObj.progMode()) + _deviceObj.individualAddress(newaddress); +} + +void BauSystemB::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber) +{ + // If the received serial number matches our serial number + // then store the received new individual address in the device object + if (!memcmp(knxSerialNumber, _deviceObj.propertyData(PID_SERIAL_NUMBER), 6)) + _deviceObj.individualAddress(newIndividualAddress); +} + +void BauSystemB::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* knxSerialNumber) +{ + // If the received serial number matches our serial number + // then send a response with the serial number. The domain address is set to 0 for closed media. + // An open medium BAU has to override this method and provide a proper domain address. + if (!memcmp(knxSerialNumber, _deviceObj.propertyData(PID_SERIAL_NUMBER), 6)) + { + uint8_t emptyDomainAddress[2] = {0x00}; + applicationLayer().IndividualAddressSerialNumberReadResponse(priority, hopType, secCtrl, emptyDomainAddress, knxSerialNumber); + } +} + +void BauSystemB::addSaveRestore(SaveRestore* obj) +{ + _memory.addSaveRestore(obj); +} + +bool BauSystemB::restartRequest(uint16_t asap, const SecurityControl secCtrl) +{ + if (applicationLayer().isConnected()) + return false; + + _restartState = Connecting; // order important, has to be set BEFORE connectRequest + _restartSecurity = secCtrl; + applicationLayer().connectRequest(asap, SystemPriority); + applicationLayer().deviceDescriptorReadRequest(AckRequested, SystemPriority, NetworkLayerParameter, asap, secCtrl, 0); + return true; +} + +void BauSystemB::connectConfirm(uint16_t tsap) +{ + if (_restartState == Connecting) + { + /* restart connection is confirmed, go to the next state */ + _restartState = Connected; + _restartDelay = millis(); + } + else + { + _restartState = Idle; + } +} + +void BauSystemB::nextRestartState() +{ + switch (_restartState) + { + case Idle: + /* inactive state, do nothing */ + break; + + case Connecting: + /* wait for connection, we do nothing here */ + break; + + case Connected: + + /* connection confirmed, we send restartRequest, but we wait a moment (sending ACK etc)... */ + if (millis() - _restartDelay > 30) + { + applicationLayer().restartRequest(AckRequested, SystemPriority, NetworkLayerParameter, _restartSecurity); + _restartState = Restarted; + _restartDelay = millis(); + } + + break; + + case Restarted: + + /* restart is finished, we send a disconnect */ + if (millis() - _restartDelay > 30) + { + applicationLayer().disconnectRequest(SystemPriority); + _restartState = Idle; + } + + default: + break; + } +} + +void BauSystemB::systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength) +{ + uint8_t operand; + + popByte(operand, testInfo + 1); // First byte (+ 0) contains only 4 reserved bits (0) + + // See KNX spec. 3.5.2 p.33 (Management Procedures: Procedures with A_SystemNetworkParameter_Read) + switch ((NmReadSerialNumberType)operand) + { + case NM_Read_SerialNumber_By_ProgrammingMode: // NM_Read_SerialNumber_By_ProgrammingMode + + // Only send a reply if programming mode is on + if (_deviceObj.progMode() && (objectType == OT_DEVICE) && (propertyId == PID_SERIAL_NUMBER)) + { + // Send reply. testResult data is KNX serial number + applicationLayer().systemNetworkParameterReadResponse(priority, hopType, secCtrl, objectType, propertyId, + testInfo, testInfoLength, (uint8_t*)_deviceObj.propertyData(PID_SERIAL_NUMBER), 6); + } + + break; + + case NM_Read_SerialNumber_By_ExFactoryState: // NM_Read_SerialNumber_By_ExFactoryState + break; + + case NM_Read_SerialNumber_By_PowerReset: // NM_Read_SerialNumber_By_PowerReset + break; + + case NM_Read_SerialNumber_By_ManufacturerSpecific: // Manufacturer specific use of A_SystemNetworkParameter_Read + break; + } +} + +void BauSystemB::systemNetworkParameterReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength, bool status) +{ +} + +void BauSystemB::propertyValueRead(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t** data, uint32_t& length) +{ + uint32_t size = 0; + uint8_t elementCount = numberOfElements; + + InterfaceObject* obj = getInterfaceObject(objectType, objectInstance); + + if (obj) + { + uint8_t elementSize = obj->propertySize((PropertyID)propertyId); + + if (startIndex > 0) + size = elementSize * numberOfElements; + else + size = sizeof(uint16_t); // size of property array entry 0 which contains the current number of elements + + *data = new uint8_t [size]; + obj->readProperty((PropertyID)propertyId, startIndex, elementCount, *data); + } + else + { + elementCount = 0; + *data = nullptr; + } + + numberOfElements = elementCount; + length = size; +} + +void BauSystemB::propertyValueWrite(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t* data, uint32_t length) +{ + InterfaceObject* obj = getInterfaceObject(objectType, objectInstance); + + if (obj) + obj->writeProperty((PropertyID)propertyId, startIndex, data, numberOfElements); + else + numberOfElements = 0; +} + +Memory& BauSystemB::memory() +{ + return _memory; +} + +void BauSystemB::versionCheckCallback(VersionCheckCallback func) +{ + _memory.versionCheckCallback(func); +} + +VersionCheckCallback BauSystemB::versionCheckCallback() +{ + return _memory.versionCheckCallback(); +} + +void BauSystemB::beforeRestartCallback(BeforeRestartCallback func) +{ + _beforeRestart = func; +} + +BeforeRestartCallback BauSystemB::beforeRestartCallback() +{ + return _beforeRestart; +} + +void BauSystemB::functionPropertyCallback(FunctionPropertyCallback func) +{ + _functionProperty = func; +} + +FunctionPropertyCallback BauSystemB::functionPropertyCallback() +{ + return _functionProperty; +} +void BauSystemB::functionPropertyStateCallback(FunctionPropertyCallback func) +{ + _functionPropertyState = func; +} + +FunctionPropertyCallback BauSystemB::functionPropertyStateCallback() +{ + return _functionPropertyState; +} \ No newline at end of file diff --git a/components/knx/src/knx/bau_systemB.h b/components/knx/src/knx/bau_systemB.h new file mode 100644 index 0000000..576dac7 --- /dev/null +++ b/components/knx/src/knx/bau_systemB.h @@ -0,0 +1,133 @@ +#pragma once + +#include "config.h" +#include "bau.h" +#include "security_interface_object.h" +#include "application_program_object.h" +#include "application_layer.h" +#include "secure_application_layer.h" +#include "transport_layer.h" +#include "network_layer.h" +#include "data_link_layer.h" +#include "platform.h" +#include "memory.h" + +class BauSystemB : protected BusAccessUnit +{ + public: + BauSystemB(Platform& platform); + virtual void loop() = 0; + virtual bool configured() = 0; + virtual bool enabled() = 0; + virtual void enabled(bool value) = 0; + + Platform& platform(); + ApplicationProgramObject& parameters(); + DeviceObject& deviceObject(); + + Memory& memory(); + void readMemory(); + void writeMemory(); + void addSaveRestore(SaveRestore* obj); + + bool restartRequest(uint16_t asap, const SecurityControl secCtrl); + uint8_t checkmasterResetValidity(EraseCode eraseCode, uint8_t channel); + + void propertyValueRead(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t** data, uint32_t& length) override; + void propertyValueWrite(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, + uint8_t& numberOfElements, uint16_t startIndex, + uint8_t* data, uint32_t length) override; + void versionCheckCallback(VersionCheckCallback func); + VersionCheckCallback versionCheckCallback(); + void beforeRestartCallback(BeforeRestartCallback func); + BeforeRestartCallback beforeRestartCallback(); + void functionPropertyCallback(FunctionPropertyCallback func); + FunctionPropertyCallback functionPropertyCallback(); + void functionPropertyStateCallback(FunctionPropertyCallback func); + FunctionPropertyCallback functionPropertyStateCallback(); + + protected: + virtual ApplicationLayer& applicationLayer() = 0; + virtual InterfaceObject* getInterfaceObject(uint8_t idx) = 0; + virtual InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance) = 0; + + void memoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data) override; + void memoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress) override; + void memoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryRouterReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data); + void memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress); + // + void memoryExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* data) override; + void memoryExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress) override; + void deviceDescriptorReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t descriptorType) override; + void restartRequestIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, RestartType restartType, EraseCode eraseCode, uint8_t channel) override; + void authorizeIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint32_t key) override; + void userMemoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress) override; + void userMemoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint32_t memoryAddress, uint8_t* memoryData) override; + void propertyDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t propertyIndex) override; + void propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex) override; + void propertyValueWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) override; + void propertyValueExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool confirmed); + void propertyValueReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) override; + void propertyValueExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) override; + void functionPropertyCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t* data, uint8_t length) override; + void functionPropertyStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, + uint8_t propertyId, uint8_t* data, uint8_t length) override; + void functionPropertyExtCommandIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t* data, uint8_t length) override; + void functionPropertyExtStateIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ObjectType objectType, uint8_t objectInstance, + uint8_t propertyId, uint8_t* data, uint8_t length) override; + void individualAddressReadIndication(HopCountType hopType, const SecurityControl& secCtrl) override; + void individualAddressWriteIndication(HopCountType hopType, const SecurityControl& secCtrl, uint16_t newaddress) override; + void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t newIndividualAddress, + uint8_t* knxSerialNumber) override; + void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* knxSerialNumber) override; + void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testinfoLength) override; + void systemNetworkParameterReadLocalConfirm(Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint16_t objectType, + uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength, bool status) override; + void connectConfirm(uint16_t tsap) override; + + void nextRestartState(); + virtual void doMasterReset(EraseCode eraseCode, uint8_t channel); + + enum RestartState + { + Idle, + Connecting, + Connected, + Restarted + }; + + Memory _memory; + DeviceObject _deviceObj; + ApplicationProgramObject _appProgram; + Platform& _platform; + RestartState _restartState = Idle; + SecurityControl _restartSecurity; + uint32_t _restartDelay = 0; + BeforeRestartCallback _beforeRestart = 0; + FunctionPropertyCallback _functionProperty = 0; + FunctionPropertyCallback _functionPropertyState = 0; +}; diff --git a/components/knx/src/knx/bau_systemB_coupler.cpp b/components/knx/src/knx/bau_systemB_coupler.cpp new file mode 100644 index 0000000..e71164f --- /dev/null +++ b/components/knx/src/knx/bau_systemB_coupler.cpp @@ -0,0 +1,60 @@ +#include "bau_systemB_coupler.h" +#include "bits.h" +#include +#include + +BauSystemBCoupler::BauSystemBCoupler(Platform& platform) : + BauSystemB(platform), + _platform(platform), +#ifdef USE_DATASECURE + _appLayer(_deviceObj, _secIfObj, *this), +#else + _appLayer(*this), +#endif + _transLayer(_appLayer), + _netLayer(_deviceObj, _transLayer) +{ + _appLayer.transportLayer(_transLayer); + _transLayer.networkLayer(_netLayer); + _memory.addSaveRestore(&_deviceObj); +#ifdef USE_DATASECURE + _memory.addSaveRestore(&_secIfObj); +#endif +} + +ApplicationLayer& BauSystemBCoupler::applicationLayer() +{ + return _appLayer; +} + +void BauSystemBCoupler::loop() +{ + _transLayer.loop(); +#ifdef USE_DATASECURE + _appLayer.loop(); +#endif +} + +bool BauSystemBCoupler::configured() +{ + // _configured is set to true initially, if the device was configured with ETS it will be set to true after restart + + if (!_configured) + return false; + + _configured = _appProgram.loadState() == LS_LOADED; +#ifdef USE_DATASECURE + _configured &= _secIfObj.loadState() == LS_LOADED; +#endif + + return _configured; +} + +void BauSystemBCoupler::doMasterReset(EraseCode eraseCode, uint8_t channel) +{ + BauSystemB::doMasterReset(eraseCode, channel); + +#ifdef USE_DATASECURE + _secIfObj.masterReset(eraseCode, channel); +#endif +} diff --git a/components/knx/src/knx/bau_systemB_coupler.h b/components/knx/src/knx/bau_systemB_coupler.h new file mode 100644 index 0000000..4fea4f4 --- /dev/null +++ b/components/knx/src/knx/bau_systemB_coupler.h @@ -0,0 +1,40 @@ +#pragma once + +#include "config.h" +#include "bau_systemB.h" +#include "device_object.h" +#include "security_interface_object.h" +#include "application_program_object.h" +#include "router_object.h" +#include "application_layer.h" +#include "secure_application_layer.h" +#include "transport_layer.h" +#include "network_layer_coupler.h" +#include "data_link_layer.h" +#include "platform.h" +#include "memory.h" + +class BauSystemBCoupler : public BauSystemB +{ + public: + BauSystemBCoupler(Platform& platform); + void loop() override; + bool configured() override; + + protected: + ApplicationLayer& applicationLayer() override; + + void doMasterReset(EraseCode eraseCode, uint8_t channel) override; + + Platform& _platform; + +#ifdef USE_DATASECURE + SecureApplicationLayer _appLayer; + SecurityInterfaceObject _secIfObj; +#else + ApplicationLayer _appLayer; +#endif + TransportLayer _transLayer; + NetworkLayerCoupler _netLayer; + bool _configured = true; +}; diff --git a/components/knx/src/knx/bau_systemB_device.cpp b/components/knx/src/knx/bau_systemB_device.cpp new file mode 100644 index 0000000..a852d4f --- /dev/null +++ b/components/knx/src/knx/bau_systemB_device.cpp @@ -0,0 +1,262 @@ +#include "bau_systemB_device.h" +#include "bits.h" +#include +#include + +BauSystemBDevice::BauSystemBDevice(Platform& platform) : + BauSystemB(platform), + _addrTable(_memory), + _assocTable(_memory), _groupObjTable(_memory), +#ifdef USE_DATASECURE + _appLayer(_deviceObj, _secIfObj, *this), +#else + _appLayer(*this), +#endif + _transLayer(_appLayer), _netLayer(_deviceObj, _transLayer) +{ + _appLayer.transportLayer(_transLayer); + _appLayer.associationTableObject(_assocTable); +#ifdef USE_DATASECURE + _appLayer.groupAddressTable(_addrTable); +#endif + _transLayer.networkLayer(_netLayer); + _transLayer.groupAddressTable(_addrTable); + + _memory.addSaveRestore(&_deviceObj); + _memory.addSaveRestore(&_groupObjTable); // changed order for better memory management + _memory.addSaveRestore(&_addrTable); + _memory.addSaveRestore(&_assocTable); +#ifdef USE_DATASECURE + _memory.addSaveRestore(&_secIfObj); +#endif +} + +ApplicationLayer& BauSystemBDevice::applicationLayer() +{ + return _appLayer; +} + +GroupObjectTableObject& BauSystemBDevice::groupObjectTable() +{ + return _groupObjTable; +} + +void BauSystemBDevice::loop() +{ + _transLayer.loop(); + sendNextGroupTelegram(); + nextRestartState(); +#ifdef USE_DATASECURE + _appLayer.loop(); +#endif + _memory.loop(); +} + +void BauSystemBDevice::sendNextGroupTelegram() +{ + if (!configured()) + return; + + static uint16_t startIdx = 1; + + GroupObjectTableObject& table = _groupObjTable; + uint16_t objCount = table.entryCount(); + + for (uint16_t asap = startIdx; asap <= objCount; asap++) + { + GroupObject& go = table.get(asap); + + ComFlag flag = go.commFlag(); + + if (flag != ReadRequest && flag != WriteRequest) + continue; + + if (flag == WriteRequest) + { +#ifdef SMALL_GROUPOBJECT + GroupObject::processClassCallback(go); +#else + GroupObjectUpdatedHandler handler = go.callback(); + + if (handler) + handler(go); + +#endif + } + + if (!go.communicationEnable()) + { + go.commFlag(Ok); + continue; + } + + SecurityControl goSecurity; + goSecurity.toolAccess = false; // Secured group communication never uses the toolkey. ETS knows all keys, also the group keys. + +#ifdef USE_DATASECURE + // Get security flags from Security Interface Object for this group object + goSecurity.dataSecurity = _secIfObj.getGroupObjectSecurity(asap); +#else + goSecurity.dataSecurity = DataSecurity::None; +#endif + + if (flag == WriteRequest && go.transmitEnable()) + { + uint8_t* data = go.valueRef(); + _appLayer.groupValueWriteRequest(AckRequested, asap, go.priority(), NetworkLayerParameter, goSecurity, data, + go.sizeInTelegram()); + } + else if (flag == ReadRequest) + { + _appLayer.groupValueReadRequest(AckRequested, asap, go.priority(), NetworkLayerParameter, goSecurity); + } + + go.commFlag(Transmitting); + + startIdx = asap + 1; + return; + } + + startIdx = 1; +} + +void BauSystemBDevice::updateGroupObject(GroupObject& go, uint8_t* data, uint8_t length) +{ + uint8_t* goData = go.valueRef(); + + if (length != go.valueSize()) + { + go.commFlag(Error); + return; + } + + memcpy(goData, data, length); + + if (go.commFlag() != WriteRequest) + { + go.commFlag(Updated); +#ifdef SMALL_GROUPOBJECT + GroupObject::processClassCallback(go); +#else + GroupObjectUpdatedHandler handler = go.callback(); + + if (handler) + handler(go); + +#endif + } + else + { + go.commFlag(Updated); + } +} + +bool BauSystemBDevice::configured() +{ + // _configured is set to true initially, if the device was configured with ETS it will be set to true after restart + + if (!_configured) + return false; + + _configured = _groupObjTable.loadState() == LS_LOADED + && _addrTable.loadState() == LS_LOADED + && _assocTable.loadState() == LS_LOADED + && _appProgram.loadState() == LS_LOADED; + +#ifdef USE_DATASECURE + _configured &= _secIfObj.loadState() == LS_LOADED; +#endif + + return _configured; +} + +void BauSystemBDevice::doMasterReset(EraseCode eraseCode, uint8_t channel) +{ + BauSystemB::doMasterReset(eraseCode, channel); + + _addrTable.masterReset(eraseCode, channel); + _assocTable.masterReset(eraseCode, channel); + _groupObjTable.masterReset(eraseCode, channel); +#ifdef USE_DATASECURE + _secIfObj.masterReset(eraseCode, channel); +#endif +} + +void BauSystemBDevice::groupValueWriteLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength, bool status) +{ + GroupObject& go = _groupObjTable.get(asap); + + if (status) + go.commFlag(Ok); + else + go.commFlag(Error); +} + +void BauSystemBDevice::groupValueReadLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, bool status) +{ + GroupObject& go = _groupObjTable.get(asap); + + if (status) + go.commFlag(Ok); + else + go.commFlag(Error); +} + +void BauSystemBDevice::groupValueReadIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl) +{ +#ifdef USE_DATASECURE + DataSecurity requiredGoSecurity; + + // Get security flags from Security Interface Object for this group object + requiredGoSecurity = _secIfObj.getGroupObjectSecurity(asap); + + if (secCtrl.dataSecurity != requiredGoSecurity) + { + println("GroupValueRead: access denied due to wrong security flags"); + return; + } + +#endif + + GroupObject& go = _groupObjTable.get(asap); + + if (!go.communicationEnable() || !go.readEnable()) + return; + + uint8_t* data = go.valueRef(); + _appLayer.groupValueReadResponse(AckRequested, asap, priority, hopType, secCtrl, data, go.sizeInTelegram()); +} + +void BauSystemBDevice::groupValueReadAppLayerConfirm(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, + uint8_t dataLength) +{ + GroupObject& go = _groupObjTable.get(asap); + + if (!go.communicationEnable() || !go.responseUpdateEnable()) + return; + + updateGroupObject(go, data, dataLength); +} + +void BauSystemBDevice::groupValueWriteIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t dataLength) +{ +#ifdef USE_DATASECURE + DataSecurity requiredGoSecurity; + + // Get security flags from Security Interface Object for this group object + requiredGoSecurity = _secIfObj.getGroupObjectSecurity(asap); + + if (secCtrl.dataSecurity != requiredGoSecurity) + { + println("GroupValueWrite: access denied due to wrong security flags"); + return; + } + +#endif + GroupObject& go = _groupObjTable.get(asap); + + if (!go.communicationEnable() || !go.writeEnable()) + return; + + updateGroupObject(go, data, dataLength); +} diff --git a/components/knx/src/knx/bau_systemB_device.h b/components/knx/src/knx/bau_systemB_device.h new file mode 100644 index 0000000..4e5e283 --- /dev/null +++ b/components/knx/src/knx/bau_systemB_device.h @@ -0,0 +1,57 @@ +#pragma once + +#include "config.h" +#include "bau_systemB.h" +#include "device_object.h" +#include "address_table_object.h" +#include "association_table_object.h" +#include "group_object_table_object.h" +#include "security_interface_object.h" +#include "application_program_object.h" +#include "application_layer.h" +#include "secure_application_layer.h" +#include "transport_layer.h" +#include "network_layer_device.h" +#include "data_link_layer.h" +#include "platform.h" +#include "memory.h" + +class BauSystemBDevice : public BauSystemB +{ + public: + BauSystemBDevice(Platform& platform); + void loop() override; + bool configured() override; + GroupObjectTableObject& groupObjectTable(); + + protected: + ApplicationLayer& applicationLayer() override; + + void groupValueWriteLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* data, uint8_t dataLength, bool status) override; + void groupValueReadLocalConfirm(AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, bool status) override; + void groupValueReadIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl) override; + void groupValueReadAppLayerConfirm(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* data, uint8_t dataLength) override; + void groupValueWriteIndication(uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, + uint8_t* data, uint8_t dataLength) override; + + void sendNextGroupTelegram(); + void updateGroupObject(GroupObject& go, uint8_t* data, uint8_t length); + + void doMasterReset(EraseCode eraseCode, uint8_t channel) override; + + AddressTableObject _addrTable; + AssociationTableObject _assocTable; + GroupObjectTableObject _groupObjTable; +#ifdef USE_DATASECURE + SecureApplicationLayer _appLayer; + SecurityInterfaceObject _secIfObj; +#else + ApplicationLayer _appLayer; +#endif + TransportLayer _transLayer; + NetworkLayerDevice _netLayer; + + bool _configured = true; +}; diff --git a/components/knx/src/knx/bits.cpp b/components/knx/src/knx/bits.cpp new file mode 100644 index 0000000..4018ca7 --- /dev/null +++ b/components/knx/src/knx/bits.cpp @@ -0,0 +1,364 @@ +#include "bits.h" +#include // for memcpy() + +const uint8_t* popByte(uint8_t& b, const uint8_t* data) +{ + b = *data; + data += 1; + return data; +} + +#ifndef KNX_NO_PRINT +void printHex(const char* suffix, const uint8_t* data, size_t length, bool newline) +{ + print(suffix); + + for (size_t i = 0; i < length; i++) + { + if (data[i] < 0x10) + { + print("0"); + } + + print(data[i], HEX); + print(" "); + } + + if (newline) + { + println(); + } +} +#endif + +const uint8_t* popWord(uint16_t& w, const uint8_t* data) +{ + w = getWord(data); + data += 2; + return data; +} + +const uint8_t* popInt(uint32_t& i, const uint8_t* data) +{ + i = getInt(data); + data += 4; + return data; +} + +const uint8_t* popByteArray(uint8_t* dst, uint32_t size, const uint8_t* data) +{ + for (uint32_t i = 0; i < size; i++) + dst[i] = data[i]; + + data += size; + return data; +} + +uint8_t* pushByte(uint8_t b, uint8_t* data) +{ + data[0] = b; + data += 1; + return data; +} + +uint8_t* pushWord(uint16_t w, uint8_t* data) +{ + data[0] = ((w >> 8) & 0xff); + data[1] = (w & 0xff); + data += 2; + return data; +} + +uint8_t* pushInt(uint32_t i, uint8_t* data) +{ + data[0] = ((i >> 24) & 0xff); + data[1] = ((i >> 16) & 0xff); + data[2] = ((i >> 8) & 0xff); + data[3] = (i & 0xff); + data += 4; + return data; +} + +uint8_t* pushByteArray(const uint8_t* src, uint32_t size, uint8_t* data) +{ + for (uint32_t i = 0; i < size; i++) + data[i] = src[i]; + + data += size; + return data; +} + +uint16_t getWord(const uint8_t* data) +{ + return (data[0] << 8) + data[1]; +} + +uint32_t getInt(const uint8_t* data) +{ + return (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]; +} + +void sixBytesFromUInt64(uint64_t num, uint8_t* toByteArray) +{ + toByteArray[0] = ((num >> 40) & 0xff); + toByteArray[1] = ((num >> 32) & 0xff); + toByteArray[2] = ((num >> 24) & 0xff); + toByteArray[3] = ((num >> 16) & 0xff); + toByteArray[4] = ((num >> 8) & 0xff); + toByteArray[5] = (num & 0xff); +} + +uint64_t sixBytesToUInt64(uint8_t* data) +{ + uint64_t l = 0; + + for (uint8_t i = 0; i < 6; i++) + { + l = (l << 8) + data[i]; + } + + return l; +} + +// The CRC of the Memory Control Block Table Property is a CRC16-CCITT with the following +// parameters: +// Width = 16 bit +// Truncated polynomial = 1021h +// Initial value = FFFFh +// Input date is NOT reflected. +// Output CRC is NOT reflected. +// No XOR is performed on the output CRC. +// EXAMPLE The correct CRC16-CCITT of the string ‘123456789’ is E5CCh. + +uint16_t crc16Ccitt(uint8_t* input, uint16_t length) +{ + uint32_t polynom = 0x1021; + + uint32_t result = 0xffff; + + for (uint32_t i = 0; i < 8 * ((uint32_t)length + 2); i++) + { + result <<= 1; + uint32_t nextBit; + nextBit = ((i / 8) < length) ? ((input[i / 8] >> (7 - (i % 8))) & 0x1) : 0; + result |= nextBit; + + if ((result & 0x10000) != 0) + result ^= polynom; + } + + return result & 0xffff; +} + +uint16_t crc16Dnp(uint8_t* input, uint16_t length) +{ + // CRC-16-DNP + // generator polynomial = 2^16 + 2^13 + 2^12 + 2^11 + 2^10 + 2^8 + 2^6 + 2^5 + 2^2 + 2^0 + uint32_t pn = 0x13d65; // 1 0011 1101 0110 0101 + + // for much data, using a lookup table would be a way faster CRC calculation + uint32_t crc = 0; + + for (uint32_t i = 0; i < length; i++) + { + uint8_t bite = input[i] & 0xff; + + for (uint8_t b = 8; b -- > 0;) + { + bool bit = ((bite >> b) & 1) == 1; + bool one = (crc >> 15 & 1) == 1; + crc <<= 1; + + if (one ^ bit) + crc ^= pn; + } + } + + return (~crc) & 0xffff; +} + +// Produce Arduino print and println in ESP IDF for ESP32 family using printf(). +#ifndef ARDUINO +#ifdef ESP_PLATFORM + // Helper function to print a number in binary format + static void print_binary(unsigned long long n) + { + if (n == 0) + { + printf("0"); + return; + } + + // Buffer for the maximum possible bits in an unsigned long long + char binary_string[65]; + int i = 0; + while (n > 0) + { + binary_string[i++] = (n % 2) + '0'; + n /= 2; + } + binary_string[i] = '\0'; + + // Reverse the string to get the correct binary representation + for (int j = 0; j < i / 2; ++j) + { + char temp = binary_string[j]; + binary_string[j] = binary_string[i - j - 1]; + binary_string[i - j - 1] = temp; + } + printf("%s", binary_string); + } + +// --- print function implementations --- + +void print(const char str[]) { + printf("%s", str); +} + +void print(char c) { + printf("%c", c); +} + +void print(unsigned char b, int base) { + if (base == BIN) { + print_binary(b); + } else if (base == DEC) { + printf("%u", (unsigned int)b); + } else if (base == HEX) { + printf("%x", (unsigned int)b); + } else if (base == OCT) { + printf("%o", (unsigned int)b); + } +} + +void print(int n, int base) { + if (base == BIN) { + print_binary(n); + } else if (base == DEC) { + printf("%d", n); + } else if (base == HEX) { + printf("%x", n); + } else if (base == OCT) { + printf("%o", n); + } +} + +void print(unsigned int n, int base) { + if (base == BIN) { + print_binary(n); + } else if (base == DEC) { + printf("%u", n); + } else if (base == HEX) { + printf("%x", n); + } else if (base == OCT) { + printf("%o", n); + } +} + +void print(long n, int base) { + if (base == BIN) { + print_binary(n); + } else if (base == DEC) { + printf("%ld", n); + } else if (base == HEX) { + printf("%lx", n); + } else if (base == OCT) { + printf("%lo", n); + } +} + +void print(unsigned long n, int base) { + if (base == BIN) { + print_binary(n); + } else if (base == DEC) { + printf("%lu", n); + } else if (base == HEX) { + printf("%lx", n); + } else if (base == OCT) { + printf("%lo", n); + } +} + +void print(long long n, int base) { + if (base == BIN) { + print_binary(n); + } else if (base == DEC) { + printf("%lld", n); + } else if (base == HEX) { + printf("%llx", n); + } else if (base == OCT) { + printf("%llo", n); + } +} + +void print(unsigned long long n, int base) { + if (base == BIN) { + print_binary(n); + } else if (base == DEC) { + printf("%llu", n); + } else if (base == HEX) { + printf("%llx", n); + } else if (base == OCT) { + printf("%llo", n); + } +} + +void print(double n) { + printf("%f", n); +} + +void println(void) { + printf("\n"); +} + +void println(const char c[]) { + print(c); + println(); +} + +void println(char c) { + print(c); + println(); +} + +void println(unsigned char b, int base) { + print(b, base); + println(); +} + +void println(int num, int base) { + print(num, base); + println(); +} + +void println(unsigned int num, int base) { + print(num, base); + println(); +} + +void println(long num, int base) { + print(num, base); + println(); +} + +void println(unsigned long num, int base) { + print(num, base); + println(); +} + +void println(long long num, int base) { + print(num, base); + println(); +} + +void println(unsigned long long num, int base) { + print(num, base); + println(); +} + +void println(double num) { + print(num); + println(); +} +#endif // ESP_PLATFORM +#endif // !ARDUINO diff --git a/components/knx/src/knx/bits.h b/components/knx/src/knx/bits.h new file mode 100644 index 0000000..6eee917 --- /dev/null +++ b/components/knx/src/knx/bits.h @@ -0,0 +1,161 @@ +#pragma once + +#include +#include + +#if defined(__linux__) + #include +#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) || defined (DeviceFamily_CC13X0) + #define getbyte(x,n) (*(((uint8_t*)&(x))+n)) + #define htons(x) ( (getbyte(x,0)<<8) | getbyte(x,1) ) + #define htonl(x) ( (getbyte(x,0)<<24) | (getbyte(x,1)<<16) | (getbyte(x,2)<<8) | getbyte(x,3) ) + #define ntohs(x) htons(x) + #define ntohl(x) htonl(x) +#elif defined(LIBRETINY) + #include + #define htons(x) lwip_htons(x) + #define htonl(x) lwip_htonl(x) +#endif + +#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_STM32) || defined(LIBRETINY) + #include +#elif defined(ARDUINO_ARCH_ESP8266) + #include + #include +#elif defined(ARDUINO_ARCH_ESP32) + #include + #include +#elif defined(ESP_PLATFORM) + #include + #include + // // Define Arduino-like macros if needed for compatibility + + #define lowByte(val) ((val)&255) + #define highByte(val) (((val) >> ((sizeof(val) - 1) << 3)) & 255) + #define bitRead(val, bitno) (((val) >> (bitno)) & 1) + #define DEC 10 + #define HEX 16 + #define OCT 8 + #define BIN 2 + #define LOW 0 + #define HIGH 1 + #define CHANGE GPIO_INTR_ANYEDGE + #define FALLING GPIO_INTR_NEGEDGE + #define RISING GPIO_INTR_POSEDGE + // Implement or map Arduino-like functions if needed + uint32_t millis(); + typedef void (*IsrFuncPtr)(void); // Arduino-style + typedef void (*EspIsrFuncPtr)(void*); // ESP-IDF-style + void attachInterrupt(uint32_t pin, IsrFuncPtr callback, uint32_t mode); +#else // Non-Arduino platforms + #define lowByte(val) ((val)&255) + #define highByte(val) (((val) >> ((sizeof(val) - 1) << 3)) & 255) + #define bitRead(val, bitno) (((val) >> (bitno)) & 1) + + // print functions are implemented in the platform files + #define DEC 10 + #define HEX 16 + + #define INPUT (0x0) + #define OUTPUT (0x1) + #define INPUT_PULLUP (0x2) + #define INPUT_PULLDOWN (0x3) + + #define LOW (0x0) + #define HIGH (0x1) + #define CHANGE 2 + #define FALLING 3 + #define RISING 4 + + void delay(uint32_t millis); + void delayMicroseconds (unsigned int howLong); + uint32_t millis(); + void pinMode(uint32_t dwPin, uint32_t dwMode); + void digitalWrite(uint32_t dwPin, uint32_t dwVal); + uint32_t digitalRead(uint32_t dwPin); + typedef void (*voidFuncPtr)(void); + void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode); +#endif + +#ifndef MIN + #define MIN(a, b) ((a < b) ? (a) : (b)) +#endif + +#ifndef MAX + #define MAX(a, b) ((a > b) ? (a) : (b)) +#endif + +#ifndef ABS + #define ABS(x) ((x > 0) ? (x) : (-x)) +#endif + +#ifndef KNX_NO_PRINT + void print(const char[]); + void print(char); + void print(unsigned char, int = DEC); + void print(int, int = DEC); + void print(unsigned int, int = DEC); + void print(long, int = DEC); + void print(unsigned long, int = DEC); + void print(long long, int = DEC); + void print(unsigned long long, int = DEC); + void print(double); + + void println(const char[]); + void println(char); + void println(unsigned char, int = DEC); + void println(int, int = DEC); + void println(unsigned int, int = DEC); + void println(long, int = DEC); + void println(unsigned long, int = DEC); + void println(long long, int = DEC); + void println(unsigned long long, int = DEC); + void println(double); + void println(void); + + void printHex(const char* suffix, const uint8_t* data, size_t length, bool newline = true); +#else + #define print(...) do {} while(0) + #define println(...) do {} while(0) + #define printHex(...) do {} while(0) +#endif + +#ifdef KNX_ACTIVITYCALLBACK + #define KNX_ACTIVITYCALLBACK_DIR 0x00 + #define KNX_ACTIVITYCALLBACK_DIR_RECV 0x00 + #define KNX_ACTIVITYCALLBACK_DIR_SEND 0x01 + #define KNX_ACTIVITYCALLBACK_IPUNICAST 0x02 + #define KNX_ACTIVITYCALLBACK_NET 0x04 +#endif + +const uint8_t* popByte(uint8_t& b, const uint8_t* data); +const uint8_t* popWord(uint16_t& w, const uint8_t* data); +const uint8_t* popInt(uint32_t& i, const uint8_t* data); +const uint8_t* popByteArray(uint8_t* dst, uint32_t size, const uint8_t* data); +uint8_t* pushByte(uint8_t b, uint8_t* data); +uint8_t* pushWord(uint16_t w, uint8_t* data); +uint8_t* pushInt(uint32_t i, uint8_t* data); +uint8_t* pushByteArray(const uint8_t* src, uint32_t size, uint8_t* data); +uint16_t getWord(const uint8_t* data); +uint32_t getInt(const uint8_t* data); + +void sixBytesFromUInt64(uint64_t num, uint8_t* toByteArray); +uint64_t sixBytesToUInt64(uint8_t* data); + +uint16_t crc16Ccitt(uint8_t* input, uint16_t length); +uint16_t crc16Dnp(uint8_t* input, uint16_t length); + +enum ParameterFloatEncodings +{ + Float_Enc_DPT9 = 0, // 2 Byte. See Chapter 3.7.2 section 3.10 (Datapoint Types 2-Octet Float Value) + Float_Enc_IEEE754Single = 1, // 4 Byte. C++ float + Float_Enc_IEEE754Double = 2, // 8 Byte. C++ double +}; + + +#if defined(ARDUINO_ARCH_SAMD) + // temporary undef until framework-arduino-samd > 1.8.9 is released. See https://github.com/arduino/ArduinoCore-samd/pull/399 for a PR should will probably address this + #undef max + #undef min + // end of temporary undef +#endif diff --git a/components/knx/src/knx/callback_property.h b/components/knx/src/knx/callback_property.h new file mode 100644 index 0000000..3a99f9f --- /dev/null +++ b/components/knx/src/knx/callback_property.h @@ -0,0 +1,39 @@ +#pragma once + +#include "property.h" + +class InterfaceObject; + +template class CallbackProperty : public Property +{ + public: + CallbackProperty(T* io, PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, + uint8_t access, uint8_t (*readCallback)(T*, uint16_t, uint8_t, uint8_t*), + uint8_t (*writeCallback)(T*, uint16_t, uint8_t, const uint8_t*)) + : Property(id, writeEnable, type, maxElements, access), + _interfaceObject(io), _readCallback(readCallback), _writeCallback(writeCallback) + {} + CallbackProperty(T* io, PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, + uint8_t access, uint8_t (*readCallback)(T*, uint16_t, uint8_t, uint8_t*)) + : Property(id, writeEnable, type, maxElements, access), _interfaceObject(io), _readCallback(readCallback) + {} + + uint8_t read(uint16_t start, uint8_t count, uint8_t* data) const override + { + if (count == 0 || _readCallback == nullptr || start > _maxElements || start + count > _maxElements + 1) + return 0; + + return _readCallback(_interfaceObject, start, count, data); + } + uint8_t write(uint16_t start, uint8_t count, const uint8_t* data) override + { + if (count == 0 || start > _maxElements || start + count > _maxElements + 1 || _writeCallback == nullptr) + return 0; + + return _writeCallback(_interfaceObject, start, count, data); + } + private: + T* _interfaceObject = nullptr; + uint8_t (*_readCallback)(T*, uint16_t, uint8_t, uint8_t*) = nullptr; + uint8_t (*_writeCallback)(T*, uint16_t, uint8_t, const uint8_t*) = nullptr; +}; diff --git a/components/knx/src/knx/cemi_frame.cpp b/components/knx/src/knx/cemi_frame.cpp new file mode 100644 index 0000000..2b04e7a --- /dev/null +++ b/components/knx/src/knx/cemi_frame.cpp @@ -0,0 +1,403 @@ +#include "cemi_frame.h" +#include "bits.h" +#include "string.h" +#include + +/* +cEMI Frame Format + + +--------+--------+--------+--------+---------+---------+--------+---------+ + | _data | + +--------+--------+--------+--------+---------+---------+--------+---------+ + | LPDU | + +--------+--------+--------+--------+---------+---------+--------+---------+ + | NPDU | + +---------+--------+--------+--------+--------+---------+---------+--------+---------+ + | Header | Msg |Add.Info| Ctrl 1 | Ctrl 2 | Source | Dest. | Data | TPDU | + | | Code | Length | | | Address | Address | Length | APDU | + +---------+--------+--------+--------+--------+---------+---------+--------+---------+ + 6 bytes 1 byte 1 byte 1 byte 1 byte 2 bytes 2 bytes 1 byte n bytes + + Header = See below the structure of a cEMI header + Message Code = See below. On Appendix A is the list of all existing EMI and cEMI codes + Add.Info Length = 0x00 - no additional info + Control Field 1 = + Control Field 2 = + Source Address = 0x0000 - filled in by router/gateway with its source address which is + part of the KNX subnet + Dest. Address = KNX group or individual address (2 byte) + Data Length = Number of bytes of data in the APDU excluding the TPCI/APCI bits + APDU = Application Protocol Data Unit - the actual payload including transport + protocol control information (TPCI), application protocol control + information (APCI) and data passed as an argument from higher layers of + the KNX communication stack + +Control Field 1 + + Bit | + ------+--------------------------------------------------------------- + 7 | Frame Type - 0x0 for extended frame + | 0x1 for standard frame + ------+--------------------------------------------------------------- + 6 | Reserved + | + ------+--------------------------------------------------------------- + 5 | Repeat Flag - 0x0 repeat frame on medium in case of an error + | 0x1 do not repeat + ------+--------------------------------------------------------------- + 4 | System Broadcast - 0x0 system broadcast + | 0x1 broadcast + ------+--------------------------------------------------------------- + 3 | Priority - 0x0 system + | 0x1 normal + ------+ 0x2 urgent + 2 | 0x3 low + | + ------+--------------------------------------------------------------- + 1 | Acknowledge Request - 0x0 no ACK requested + | (L_Data.req) 0x1 ACK requested + ------+--------------------------------------------------------------- + 0 | Confirm - 0x0 no error + | (L_Data.con) - 0x1 error + ------+--------------------------------------------------------------- + + Control Field 2 + + Bit | + ------+--------------------------------------------------------------- + 7 | Destination Address Type - 0x0 individual address + | - 0x1 group address + ------+--------------------------------------------------------------- + 6-4 | Hop Count (0-7) + ------+--------------------------------------------------------------- + 3-0 | Extended Frame Format - 0x0 standard frame + ------+--------------------------------------------------------------- +*/ + +CemiFrame::CemiFrame(uint8_t* data, uint16_t length) + : _npdu(data + data[1] + NPDU_LPDU_DIFF, *this), + _tpdu(data + data[1] + TPDU_LPDU_DIFF, *this), + _apdu(data + data[1] + APDU_LPDU_DIFF, *this) +{ + _data = data; + _ctrl1 = data + data[1] + CEMI_HEADER_SIZE; + _length = length; +} + +CemiFrame::CemiFrame(uint8_t apduLength) + : _data(buffer), + _npdu(_data + NPDU_LPDU_DIFF, *this), + _tpdu(_data + TPDU_LPDU_DIFF, *this), + _apdu(_data + APDU_LPDU_DIFF, *this) +{ + _ctrl1 = _data + CEMI_HEADER_SIZE; + + memset(_data, 0, apduLength + APDU_LPDU_DIFF); + _ctrl1[0] |= Broadcast; + _npdu.octetCount(apduLength); + _length = _npdu.length() + NPDU_LPDU_DIFF; +} + +CemiFrame::CemiFrame(const CemiFrame& other) + : _data(buffer), + _npdu(_data + NPDU_LPDU_DIFF, *this), + _tpdu(_data + TPDU_LPDU_DIFF, *this), + _apdu(_data + APDU_LPDU_DIFF, *this) +{ + _ctrl1 = _data + CEMI_HEADER_SIZE; + _length = other._length; + + memcpy(_data, other._data, other.totalLenght()); +} + +CemiFrame& CemiFrame::operator=(CemiFrame other) +{ + _length = other._length; + _data = buffer; + _ctrl1 = _data + CEMI_HEADER_SIZE; + memcpy(_data, other._data, other.totalLenght()); + _npdu._data = _data + NPDU_LPDU_DIFF; + _tpdu._data = _data + TPDU_LPDU_DIFF; + _apdu._data = _data + APDU_LPDU_DIFF; + return *this; +} + + +MessageCode CemiFrame::messageCode() const +{ + return (MessageCode)_data[0]; +} + +void CemiFrame::messageCode(MessageCode msgCode) +{ + _data[0] = msgCode; +} + +uint16_t CemiFrame::totalLenght() const +{ + return _length; +} + +uint16_t CemiFrame::telegramLengthtTP() const +{ + if (frameType() == StandardFrame) + return totalLenght() - 2; /*-AddInfo -MsgCode - only one CTRL + CRC, */ + else + return totalLenght() - 1; /*-AddInfo -MsgCode + CRC, */ +} + +void CemiFrame::fillTelegramTP(uint8_t* data) +{ + uint16_t len = telegramLengthtTP(); + + if (frameType() == StandardFrame) + { + uint8_t octet5 = (_ctrl1[1] & 0xF0) | (_ctrl1[6] & 0x0F); + data[0] = _ctrl1[0]; //CTRL + memcpy(data + 1, _ctrl1 + 2, 4); // SA, DA + data[5] = octet5; // LEN; Hopcount, .. + memcpy(data + 6, _ctrl1 + 7, len - 7); // APDU + } + else + { + memcpy(data, _ctrl1, len - 1); + } + + data[len - 1] = calcCrcTP(data, len - 1); +} + +#ifdef USE_RF + +uint16_t CemiFrame::telegramLengthtRF() const +{ + return totalLenght() - 3; +} + +void CemiFrame::fillTelegramRF(uint8_t* data) +{ + uint16_t len = telegramLengthtRF(); + + // We prepare the actual KNX telegram for RF here only. + // The packaging into blocks with CRC16 (Format based on FT3 Data Link Layer (IEC 870-5)) + // is done in the RF Data Link Layer code. + // RF always uses the Extended Frame Format. However, the length field is missing (right before the APDU) + // as there is already a length field at the beginning of the raw RF frame which is also used by the + // physical layer to control the HW packet engine of the transceiver. + + data[0] = _ctrl1[1] & 0x0F; // KNX CTRL field for RF (bits 3..0 EFF only), bits 7..4 are set to 0 for asynchronous RF frames + memcpy(data + 1, _ctrl1 + 2, 4); // SA, DA + data[5] = (_ctrl1[1] & 0xF0) | ((_rfLfn & 0x7) << 1) | ((_ctrl1[0] & 0x10) >> 4); // L/NPCI field: AT, Hopcount, LFN, AET + memcpy(data + 6, _ctrl1 + 7, len - 6); // APDU + + //printHex("cEMI_fill: ", &data[0], len); +} +#endif +uint8_t* CemiFrame::data() +{ + return _data; +} + +uint16_t CemiFrame::dataLength() +{ + return _length; +} + +uint8_t CemiFrame::calcCrcTP(uint8_t* buffer, uint16_t len) +{ + uint8_t crc = 0xFF; + + for (uint16_t i = 0; i < len; i++) + crc ^= buffer[i]; + + return crc; +} + +FrameFormat CemiFrame::frameType() const +{ + return (FrameFormat)(_ctrl1[0] & StandardFrame); +} + +void CemiFrame::frameType(FrameFormat type) +{ + _ctrl1[0] &= ~StandardFrame; + _ctrl1[0] |= type; +} + +Repetition CemiFrame::repetition() const +{ + return (Repetition)(_ctrl1[0] & RepetitionAllowed); +} + +void CemiFrame::repetition(Repetition rep) +{ + _ctrl1[0] &= ~RepetitionAllowed; + _ctrl1[0] |= rep; +} + +SystemBroadcast CemiFrame::systemBroadcast() const +{ + return (SystemBroadcast)(_ctrl1[0] & Broadcast); +} + +void CemiFrame::systemBroadcast(SystemBroadcast value) +{ + _ctrl1[0] &= ~Broadcast; + _ctrl1[0] |= value; +} + +Priority CemiFrame::priority() const +{ + return (Priority)(_ctrl1[0] & LowPriority); +} + +void CemiFrame::priority(Priority value) +{ + _ctrl1[0] &= ~LowPriority; + _ctrl1[0] |= value; +} + +AckType CemiFrame::ack() const +{ + return (AckType)(_ctrl1[0] & AckRequested); +} + +void CemiFrame::ack(AckType value) +{ + _ctrl1[0] &= ~AckRequested; + _ctrl1[0] |= value; +} + +Confirm CemiFrame::confirm() const +{ + return (Confirm)(_ctrl1[0] & ConfirmError); +} + +void CemiFrame::confirm(Confirm value) +{ + _ctrl1[0] &= ~ConfirmError; + _ctrl1[0] |= value; +} + +AddressType CemiFrame::addressType() const +{ + return (AddressType)(_ctrl1[1] & GroupAddress); +} + +void CemiFrame::addressType(AddressType value) +{ + _ctrl1[1] &= ~GroupAddress; + _ctrl1[1] |= value; +} + +uint8_t CemiFrame::hopCount() const +{ + return ((_ctrl1[1] >> 4) & 0x7); +} + +void CemiFrame::hopCount(uint8_t value) +{ + _ctrl1[1] &= ~(0x7 << 4); + _ctrl1[1] |= ((value & 0x7) << 4); +} + +uint16_t CemiFrame::sourceAddress() const +{ + uint16_t addr; + popWord(addr, _ctrl1 + 2); + return addr; +} + +void CemiFrame::sourceAddress(uint16_t value) +{ + pushWord(value, _ctrl1 + 2); +} + +uint16_t CemiFrame::destinationAddress() const +{ + uint16_t addr; + popWord(addr, _ctrl1 + 4); + return addr; +} + +void CemiFrame::destinationAddress(uint16_t value) +{ + pushWord(value, _ctrl1 + 4); +} +#ifdef USE_RF +uint8_t* CemiFrame::rfSerialOrDoA() const +{ + return _rfSerialOrDoA; +} + +void CemiFrame::rfSerialOrDoA(const uint8_t* rfSerialOrDoA) +{ + _rfSerialOrDoA = (uint8_t*)rfSerialOrDoA; +} + +uint8_t CemiFrame::rfInfo() const +{ + return _rfInfo; +} + +void CemiFrame::rfInfo(uint8_t rfInfo) +{ + _rfInfo = rfInfo; +} + +uint8_t CemiFrame::rfLfn() const +{ + return _rfLfn; +} + +void CemiFrame::rfLfn(uint8_t rfLfn) +{ + _rfLfn = rfLfn; +} +#endif +NPDU& CemiFrame::npdu() +{ + return _npdu; +} + +TPDU& CemiFrame::tpdu() +{ + return _tpdu; +} + +APDU& CemiFrame::apdu() +{ + return _apdu; +} + +bool CemiFrame::valid() const +{ + uint8_t addInfoLen = _data[1]; + uint8_t apduLen = _data[_data[1] + NPDU_LPDU_DIFF]; + + if (_length != 0 && _length != (addInfoLen + apduLen + NPDU_LPDU_DIFF + 2)) + { + print("length issue, length: "); + print(_length); + print(" addInfoLen: "); + print(addInfoLen); + print(" apduLen: "); + print(apduLen); + print(" expected length: "); + println(addInfoLen + apduLen + NPDU_LPDU_DIFF + 2); + printHex("Frame: ", _data, _length, true); + + return false; + } + + if ((_ctrl1[0] & 0x40) > 0 // Bit 6 has do be 0 + || (_ctrl1[1] & 0xF) > 0 // only standard or extended frames + || _npdu.octetCount() == 0xFF // not allowed + || (_npdu.octetCount() > 15 && frameType() == StandardFrame) + ) + { + print("Other issue"); + return false; + } + + return true; +} diff --git a/components/knx/src/knx/cemi_frame.h b/components/knx/src/knx/cemi_frame.h new file mode 100644 index 0000000..03ac2a3 --- /dev/null +++ b/components/knx/src/knx/cemi_frame.h @@ -0,0 +1,94 @@ +#pragma once + +#include "knx_types.h" +#include "stdint.h" +#include "npdu.h" +#include "tpdu.h" +#include "apdu.h" +#include "config.h" + +#define NPDU_LPDU_DIFF 8 +#define TPDU_NPDU_DIFF 1 +#define APDU_TPDU_DIFF 0 +#define TPDU_LPDU_DIFF (TPDU_NPDU_DIFF + NPDU_LPDU_DIFF) +#define APDU_LPDU_DIFF (APDU_TPDU_DIFF + TPDU_NPDU_DIFF + NPDU_LPDU_DIFF) + +// Mesg Code and additional info length +#define CEMI_HEADER_SIZE 2 + +class CemiFrame +{ + friend class DataLinkLayer; + + public: + CemiFrame(uint8_t* data, uint16_t length); + CemiFrame(uint8_t apduLength); + CemiFrame(const CemiFrame& other); + CemiFrame& operator=(CemiFrame other); + + MessageCode messageCode() const; + void messageCode(MessageCode value); + uint16_t totalLenght() const; + uint16_t telegramLengthtTP() const; + void fillTelegramTP(uint8_t* data); + uint16_t telegramLengthtRF() const; + void fillTelegramRF(uint8_t* data); + uint8_t* data(); + uint16_t dataLength(); + + FrameFormat frameType() const; + void frameType(FrameFormat value); + Repetition repetition() const; + void repetition(Repetition value); + SystemBroadcast systemBroadcast() const; + void systemBroadcast(SystemBroadcast value); + Priority priority() const; + void priority(Priority value); + AckType ack() const; + void ack(AckType value); + Confirm confirm() const; + void confirm(Confirm value); + AddressType addressType() const; + void addressType(AddressType value); + uint8_t hopCount() const; + void hopCount(uint8_t value); + uint16_t sourceAddress() const; + void sourceAddress(uint16_t value); + uint16_t destinationAddress() const; + void destinationAddress(uint16_t value); + +#ifdef USE_RF + // only for RF medium + uint8_t* rfSerialOrDoA() const; + void rfSerialOrDoA(const uint8_t* rfSerialOrDoA); + uint8_t rfInfo() const; + void rfInfo(uint8_t rfInfo); + uint8_t rfLfn() const; + void rfLfn(uint8_t rfInfo); +#endif + NPDU& npdu(); + TPDU& tpdu(); + APDU& apdu(); + + uint8_t calcCrcTP(uint8_t* buffer, uint16_t len); + bool valid() const; + + private: + uint8_t buffer[0xff + NPDU_LPDU_DIFF] = {0}; //only valid of add info is zero + uint8_t* _data = 0; + uint8_t* _ctrl1 = 0; + NPDU _npdu; + TPDU _tpdu; + APDU _apdu; + uint16_t _length = 0; // only set if created from byte array + +#ifdef USE_RF + // FIXME: integrate this propery in _data + // only for RF medium + uint8_t* _rfSerialOrDoA = 0; + uint8_t _rfInfo = 0; + uint8_t _rfLfn = 0xFF; // RF Data Link layer frame number +#endif + + uint8_t _sourceInterfaceIndex; +}; diff --git a/components/knx/src/knx/cemi_server.cpp b/components/knx/src/knx/cemi_server.cpp new file mode 100644 index 0000000..a4e7bc8 --- /dev/null +++ b/components/knx/src/knx/cemi_server.cpp @@ -0,0 +1,443 @@ +#include "config.h" +#ifdef USE_CEMI_SERVER + +#include "cemi_server.h" +#include "cemi_frame.h" +#include "bau_systemB.h" +#include "usb_tunnel_interface.h" +#include "data_link_layer.h" +#include "string.h" +#include "bits.h" +#include + +CemiServer::CemiServer(BauSystemB& bau) + : _bau(bau) +#ifdef USE_USB + , + _usbTunnelInterface(*this, + _bau.deviceObject().maskVersion(), + _bau.deviceObject().manufacturerId()) +#endif +{ + // The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS), + // so that the device and the cEMI client/server connection(tunnel) can operate simultaneously. + _clientAddress = _bau.deviceObject().individualAddress() + 1; +} + +void CemiServer::dataLinkLayer(DataLinkLayer& layer) +{ + _dataLinkLayer = &layer; +} + +#ifdef KNX_TUNNELING +void CemiServer::dataLinkLayerPrimary(DataLinkLayer& layer) +{ + _dataLinkLayerPrimary = &layer; +} + +#endif +uint16_t CemiServer::clientAddress() const +{ + return _clientAddress; +} + +void CemiServer::clientAddress(uint16_t value) +{ + _clientAddress = value; +} + +void CemiServer::dataConfirmationToTunnel(CemiFrame& frame) +{ + MessageCode backupMsgCode = frame.messageCode(); + + frame.messageCode(L_data_con); + +#ifdef KNX_LOG_TUNNELING + print("L_data_con: src: "); + print(frame.sourceAddress(), HEX); + print(" dst: "); + print(frame.destinationAddress(), HEX); + + printHex(" frame: ", frame.data(), frame.dataLength()); +#endif + +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(frame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataConfirmationToTunnel(frame); +#endif + + frame.messageCode(backupMsgCode); +} + +void CemiServer::dataIndicationToTunnel(CemiFrame& frame) +{ +#ifdef USE_RF + bool isRf = _dataLinkLayer->mediumType() == DptMedium::KNX_RF; + uint8_t data[frame.dataLength() + (isRf ? 10 : 0)]; +#else + uint8_t data[frame.dataLength()]; +#endif + +#ifdef USE_RF + + if (isRf) + { + data[0] = L_data_ind; // Message Code + data[1] = 0x0A; // Total additional info length + data[2] = 0x02; // RF add. info: type + data[3] = 0x08; // RF add. info: length + data[4] = frame.rfInfo(); // RF add. info: info field (batt ok, bidir) + pushByteArray(frame.rfSerialOrDoA(), 6, &data[5]); // RF add. info:Serial or Domain Address + data[11] = frame.rfLfn(); // RF add. info: link layer frame number + memcpy(&data[12], &((frame.data())[2]), frame.dataLength() - 2); + } + else + { +#endif + memcpy(&data[0], frame.data(), frame.dataLength()); +#ifdef USE_RF + } + +#endif + + CemiFrame tmpFrame(data, sizeof(data)); + +#ifdef KNX_LOG_TUNNELING + print("ToTunnel "); + print("L_data_ind: src: "); + print(tmpFrame.sourceAddress(), HEX); + print(" dst: "); + print(tmpFrame.destinationAddress(), HEX); + + printHex(" frame: ", tmpFrame.data(), tmpFrame.dataLength()); +#endif + tmpFrame.apdu().type(); + +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(tmpFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataIndicationToTunnel(frame); +#endif +} + +void CemiServer::frameReceived(CemiFrame& frame) +{ + switch (frame.messageCode()) + { + case L_data_req: + { + handleLData(frame); + break; + } + + case M_PropRead_req: + { + handleMPropRead(frame); + break; + } + + case M_PropWrite_req: + { + handleMPropWrite(frame); + break; + } + + case M_FuncPropCommand_req: + { + println("M_FuncPropCommand_req not implemented"); + break; + } + + case M_FuncPropStateRead_req: + { + println("M_FuncPropStateRead_req not implemented"); + break; + } + + case M_Reset_req: + { + handleMReset(frame); + break; + } + + // we should never receive these: server -> client + case L_data_con: + case L_data_ind: + case M_PropInfo_ind: + case M_PropRead_con: + case M_PropWrite_con: + case M_FuncPropCommand_con: + + //case M_FuncPropStateRead_con: // same value as M_FuncPropCommand_con + case M_Reset_ind: + default: + break; + } +} + +void CemiServer::handleLData(CemiFrame& frame) +{ + // Fill in the cEMI client address if the client sets + // source address to 0. +#ifndef KNX_TUNNELING + //We already set the correct IA + if (frame.sourceAddress() == 0x0000) + { + frame.sourceAddress(_clientAddress); + } + +#endif + +#ifdef USE_RF + + if (_dataLinkLayer->mediumType() == DptMedium::KNX_RF) + { + // Check if we have additional info for RF + if (((frame.data())[1] == 0x0A) && // Additional info total length: we only handle one additional info of type RF + ((frame.data())[2] == 0x02) && // Additional info type: RF + ((frame.data())[3] == 0x08) ) // Additional info length of type RF: 8 bytes (fixed) + { + frame.rfInfo((frame.data())[4]); + + // Use the values provided in the RF additonal info + if ( ((frame.data())[5] != 0x00) || ((frame.data())[6] != 0x00) || ((frame.data())[7] != 0x00) || + ((frame.data())[8] != 0x00) || ((frame.data())[9] != 0x00) || ((frame.data())[10] != 0x00) ) + { + frame.rfSerialOrDoA(&((frame.data())[5])); + } // else leave the nullptr as it is + + frame.rfLfn((frame.data())[11]); + } + + // If the cEMI client does not provide a link layer frame number (LFN), + // we use our own counter. + // Note: There is another link layer frame number counter inside the RF data link layer class! + // That counter is solely for the local application! + // If we set a LFN here, the data link layer counter is NOT used! + if (frame.rfLfn() == 0xFF) + { + // Set Data Link Layer Frame Number + frame.rfLfn(_frameNumber); + // Link Layer frame number counts 0..7 + _frameNumber = (_frameNumber + 1) & 0x7; + } + } + +#endif + +#ifdef KNX_LOG_TUNNELING + print("L_data_req: src: "); + print(frame.sourceAddress(), HEX); + print(" dst: "); + print(frame.destinationAddress(), HEX); + printHex(" frame: ", frame.data(), frame.dataLength()); +#endif + _dataLinkLayer->dataRequestFromTunnel(frame); +} + +void CemiServer::handleMPropRead(CemiFrame& frame) +{ +#ifdef KNX_LOG_TUNNELING + print("M_PropRead_req: "); +#endif + + uint16_t objectType; + popWord(objectType, &frame.data()[1]); + uint8_t objectInstance = frame.data()[3]; + uint8_t propertyId = frame.data()[4]; + uint8_t numberOfElements = frame.data()[5] >> 4; + uint16_t startIndex = frame.data()[6] | ((frame.data()[5] & 0x0F) << 8); + uint8_t* data = nullptr; + uint32_t dataSize = 0; + +#ifdef KNX_LOG_TUNNELING + print("ObjType: "); + print(objectType, DEC); + print(" ObjInst: "); + print(objectInstance, DEC); + print(" PropId: "); + print(propertyId, DEC); + print(" NoE: "); + print(numberOfElements, DEC); + print(" startIdx: "); + print(startIndex, DEC); +#endif + + // propertyValueRead() allocates memory for the data! Needs to be deleted again! + _bau.propertyValueRead((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, &data, dataSize); + + // Patch result for device address in device object + // The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS), + // so that the device and the cEMI client/server connection(tunnel) can operate simultaneously. + // KNX IP Interfaces which offer multiple simultaneous tunnel connections seem to operate the same way. + // Each tunnel has its own cEMI client address which is based on the main device address. + if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_DEVICE_ADDR) && + (numberOfElements == 1)) + { + data[0] = (uint8_t) (_clientAddress & 0xFF); + } + else if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_SUBNET_ADDR) && + (numberOfElements == 1)) + { + data[0] = (uint8_t) ((_clientAddress >> 8) & 0xFF); + } + + if (data && dataSize && numberOfElements) + { +#ifdef KNX_LOG_TUNNELING + printHex(" <- data: ", data, dataSize); +#endif + + // Prepare positive response + uint8_t responseData[7 + dataSize]; + memcpy(responseData, frame.data(), 7); + memcpy(&responseData[7], data, dataSize); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropRead_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + delete[] data; + } + else + { + // Prepare negative response + uint8_t responseData[7 + 1]; + memcpy(responseData, frame.data(), sizeof(responseData)); + responseData[7] = Void_DP; // Set cEMI error code + responseData[5] = 0; // Set Number of elements to zero + + printHex(" <- error: ", &responseData[7], 1); + println(""); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropRead_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + } +} + +void CemiServer::handleMPropWrite(CemiFrame& frame) +{ + print("M_PropWrite_req: "); + + uint16_t objectType; + popWord(objectType, &frame.data()[1]); + uint8_t objectInstance = frame.data()[3]; + uint8_t propertyId = frame.data()[4]; + uint8_t numberOfElements = frame.data()[5] >> 4; + uint16_t startIndex = frame.data()[6] | ((frame.data()[5] & 0x0F) << 8); + uint8_t* requestData = &frame.data()[7]; + uint32_t requestDataSize = frame.dataLength() - 7; + + print("ObjType: "); + print(objectType, DEC); + print(" ObjInst: "); + print(objectInstance, DEC); + print(" PropId: "); + print(propertyId, DEC); + print(" NoE: "); + print(numberOfElements, DEC); + print(" startIdx: "); + print(startIndex, DEC); + + printHex(" -> data: ", requestData, requestDataSize); + + // Patch request for device address in device object + if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_DEVICE_ADDR) && + (numberOfElements == 1)) + { + // Temporarily store new cEMI client address in memory + // We also be sent back if the client requests it again + _clientAddress = (_clientAddress & 0xFF00) | requestData[0]; + print("cEMI client address: "); + println(_clientAddress, HEX); + } + else if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_SUBNET_ADDR) && + (numberOfElements == 1)) + { + // Temporarily store new cEMI client address in memory + // We also be sent back if the client requests it again + _clientAddress = (_clientAddress & 0x00FF) | (requestData[0] << 8); + print("cEMI client address: "); + println(_clientAddress, HEX); + } + else + { + _bau.propertyValueWrite((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, requestData, requestDataSize); + } + + if (numberOfElements) + { + // Prepare positive response + uint8_t responseData[7]; + memcpy(responseData, frame.data(), sizeof(responseData)); + + println(" <- no error"); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropWrite_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + } + else + { + // Prepare negative response + uint8_t responseData[7 + 1]; + memcpy(responseData, frame.data(), sizeof(responseData)); + responseData[7] = Illegal_Command; // Set cEMI error code + responseData[5] = 0; // Set Number of elements to zero + + printHex(" <- error: ", &responseData[7], 1); + println(""); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropWrite_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + } +} + +void CemiServer::handleMReset(CemiFrame& frame) +{ + println("M_Reset_req: sending M_Reset_ind"); + // A real device reset does not work for USB or KNXNET/IP. + // Thus, M_Reset_ind is NOT mandatory for USB and KNXNET/IP. + // We just save all data to the EEPROM + _bau.writeMemory(); + // Prepare response + uint8_t responseData[1]; + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_Reset_ind); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif +} + +void CemiServer::loop() +{ +#ifdef USE_USB + _usbTunnelInterface.loop(); +#endif +} + +#endif diff --git a/components/knx/src/knx/cemi_server.h b/components/knx/src/knx/cemi_server.h new file mode 100644 index 0000000..2aac12b --- /dev/null +++ b/components/knx/src/knx/cemi_server.h @@ -0,0 +1,68 @@ +#pragma once + +#include "config.h" +#ifdef USE_CEMI_SERVER + +#include +#include "knx_types.h" +#include "usb_tunnel_interface.h" + +class BauSystemB; +class DataLinkLayer; +class CemiFrame; + +/** + * This is an implementation of the cEMI server as specified in @cite knx:3/6/3. + * Overview on page 57. + * It provides methods for the BusAccessUnit to do different things and translates this + * call to an cEMI frame and calls the correct method of the data link layer. + * It also takes calls from data link layer, decodes the submitted cEMI frames and calls the corresponding + * methods of the BusAccessUnit class. + */ +class CemiServer +{ + public: + /** + * The constructor. + * @param bau methods are called here depending of the content of the APDU + */ + CemiServer(BauSystemB& bau); + + void dataLinkLayer(DataLinkLayer& layer); +#ifdef KNX_TUNNELING + void dataLinkLayerPrimary(DataLinkLayer& layer); +#endif + + // from data link layer + // Only L_Data service + void dataIndicationToTunnel(CemiFrame& frame); + void dataConfirmationToTunnel(CemiFrame& frame); + + // From tunnel interface + void frameReceived(CemiFrame& frame); + + uint16_t clientAddress() const; + void clientAddress(uint16_t value); + + void loop(); + + private: + uint16_t _clientAddress = 0; + uint8_t _frameNumber = 0; + + void handleLData(CemiFrame& frame); + void handleMPropRead(CemiFrame& frame); + void handleMPropWrite(CemiFrame& frame); + void handleMReset(CemiFrame& frame); + + DataLinkLayer* _dataLinkLayer = nullptr; +#ifdef KNX_TUNNELING + DataLinkLayer* _dataLinkLayerPrimary = nullptr; +#endif + BauSystemB& _bau; +#ifdef USE_USB + UsbTunnelInterface _usbTunnelInterface; +#endif +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/cemi_server_object.cpp b/components/knx/src/knx/cemi_server_object.cpp new file mode 100644 index 0000000..30ef39d --- /dev/null +++ b/components/knx/src/knx/cemi_server_object.cpp @@ -0,0 +1,61 @@ +#include "config.h" +#ifdef USE_CEMI_SERVER + +#include +#include "cemi_server_object.h" +#include "bits.h" +#include "data_property.h" + +CemiServerObject::CemiServerObject() +{ + Property* properties[] = + { + new DataProperty( PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_CEMI_SERVER ), + new DataProperty( PID_MEDIUM_TYPE, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0, (uint16_t)0), + new DataProperty( PID_COMM_MODE, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0, (uint16_t)0), + new DataProperty( PID_COMM_MODES_SUPPORTED, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0, (uint16_t)0x100), + new DataProperty( PID_MEDIUM_AVAILABILITY, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0, (uint16_t)0), + // cEMI additional info types supported by this cEMI server: only 0x02 (RF Control Octet and Serial Number or DoA) + new DataProperty( PID_ADD_INFO_TYPES, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0, (uint8_t)0x02) + }; + initializeProperties(sizeof(properties), properties); +} + +void CemiServerObject::setMediumTypeAsSupported(DptMedium dptMedium) +{ + uint16_t mediaTypesSupported; + property(PID_MEDIUM_TYPE)->read(mediaTypesSupported); + + switch (dptMedium) + { + case DptMedium::KNX_IP: + mediaTypesSupported |= 1 << 1; + break; + + case DptMedium::KNX_RF: + mediaTypesSupported |= 1 << 4; + break; + + case DptMedium::KNX_TP1: + mediaTypesSupported |= 1 << 5; + break; + + case DptMedium::KNX_PL110: + mediaTypesSupported |= 1 << 2; + break; + } + + property(PID_MEDIUM_TYPE)->write(mediaTypesSupported); + // We also set the medium as available too + property(PID_MEDIUM_AVAILABILITY)->write(mediaTypesSupported); +} + +void CemiServerObject::clearSupportedMediaTypes() +{ + property(PID_MEDIUM_TYPE)->write((uint16_t) 0); + // We also set the medium as not available too + property(PID_MEDIUM_AVAILABILITY)->write((uint16_t) 0); +} + +#endif + diff --git a/components/knx/src/knx/cemi_server_object.h b/components/knx/src/knx/cemi_server_object.h new file mode 100644 index 0000000..143c7d7 --- /dev/null +++ b/components/knx/src/knx/cemi_server_object.h @@ -0,0 +1,17 @@ +#pragma once + +#include "config.h" +#ifdef USE_CEMI_SERVER + +#include "interface_object.h" + +class CemiServerObject: public InterfaceObject +{ + public: + CemiServerObject(); + + void setMediumTypeAsSupported(DptMedium dptMedium); + void clearSupportedMediaTypes(); +}; + +#endif diff --git a/components/knx/src/knx/config.h b/components/knx/src/knx/config.h new file mode 100644 index 0000000..c2a394e --- /dev/null +++ b/components/knx/src/knx/config.h @@ -0,0 +1,84 @@ +#pragma once + +#ifndef NO_KNX_CONFIG + + #ifdef ARDUINO_ARCH_SAMD + #define SPI_SS_PIN 10 + #define GPIO_GDO2_PIN 9 + #define GPIO_GDO0_PIN 7 + #else // Linux Platform (Raspberry Pi) + #define SPI_SS_PIN 8 // GPIO 8 (SPI_CE0_N) -> WiringPi: 10 -> Pin number on header: 24 + #define GPIO_GDO2_PIN 25 // GPIO 25 (GPIO_GEN6) -> WiringPi: 6 -> Pin number on header: 22 + #define GPIO_GDO0_PIN 24 // GPIO 24 (GPIO_GEN5) -> WiringPi: 5 -> Pin number on header: 18 + #endif + + // Normal devices + // TP1: 0x07B0 + // RF: 0x27B0 + // IP: 0x57B0 + //#define MASK_VERSION 0x07B0 + //#define MASK_VERSION 0x27B0 + //#define MASK_VERSION 0x57B0 + + // Couplers + // IP/TP1: 0x091A + // TP1/RF: 0x2920 + //#define MASK_VERSION 0x091A + //#define MASK_VERSION 0x2920 + + // Data Linklayer Driver Options + #if MASK_VERSION == 0x07B0 + #define USE_TP + #endif + + #if MASK_VERSION == 0x27B0 + #define USE_RF + #endif + + #if MASK_VERSION == 0x57B0 + #define USE_IP + #endif + + #if MASK_VERSION == 0x091A + #define USE_TP + #define USE_IP + #endif + + #if MASK_VERSION == 0x2920 + #define USE_TP + #define USE_RF + #endif + + // cEMI options + //#define USE_USB + //#define USE_CEMI_SERVER + #if defined(USE_USB) || defined(KNX_TUNNELING) + #define USE_CEMI_SERVER + #endif + + // KNX Data Secure Options + // Define via a compiler -D flag if required + // #define USE_DATASECURE + + // option to have GroupObjects (KO in German) use 8 bytes mangement information RAM instead of 19 bytes + // see knx-demo-small-go for example + // this option might be also set via compiler flag -DSMALL_GROUPOBJECT if required + //#define SMALL_GROUPOBJECT + + // Some defines to reduce footprint + // Do not perform conversion from KNXValue(const char*) to other types, it mainly avoids the expensive strtod + //#define KNX_NO_STRTOx_CONVERSION + // Do not print messages + //#define KNX_NO_PRINT + // Do not use SPI (Arduino variants) + //#define KNX_NO_SPI + // Do not use the default UART (Arduino variants), it must be defined by ArduinoPlatform::knxUart + // (combined with other flags (HWSERIAL_NONE for stm32) - avoid allocation of RX/TX buffers for all serial lines) + //#define KNX_NO_DEFAULT_UART + +#endif + +#if !defined(MASK_VERSION) + #error MASK_VERSION must be defined! See config.h for possible values! +#endif + diff --git a/components/knx/src/knx/data_link_layer.cpp b/components/knx/src/knx/data_link_layer.cpp new file mode 100644 index 0000000..f2a88c6 --- /dev/null +++ b/components/knx/src/knx/data_link_layer.cpp @@ -0,0 +1,309 @@ +#include "data_link_layer.h" + +#include "bits.h" +#include "platform.h" +#include "device_object.h" +#include "cemi_server.h" +#include "cemi_frame.h" + + +void DataLinkLayerCallbacks::activity(uint8_t info) +{ + if (_activityCallback) + _activityCallback(info); +} + +void DataLinkLayerCallbacks::setActivityCallback(ActivityCallback activityCallback) +{ + _activityCallback = activityCallback; +} + +DataLinkLayer::DataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, Platform& platform) : + _deviceObject(devObj), _networkLayerEntity(netLayerEntity), _platform(platform) +{ +#ifdef KNX_ACTIVITYCALLBACK + _netIndex = netLayerEntity.getEntityIndex(); +#endif +} + +#ifdef USE_CEMI_SERVER + +void DataLinkLayer::cemiServer(CemiServer& cemiServer) +{ + _cemiServer = &cemiServer; +} + +#ifdef KNX_TUNNELING +void DataLinkLayer::dataRequestToTunnel(CemiFrame& frame) +{ + println("default dataRequestToTunnel"); +} + +void DataLinkLayer::dataConfirmationToTunnel(CemiFrame& frame) +{ + println("default dataConfirmationToTunnel"); +} + +void DataLinkLayer::dataIndicationToTunnel(CemiFrame& frame) +{ + println("default dataIndicationToTunnel"); +} + +bool DataLinkLayer::isTunnelAddress(uint16_t addr) +{ + println("default IsTunnelAddress"); + return false; +} +#endif + +void DataLinkLayer::dataRequestFromTunnel(CemiFrame& frame) +{ + _cemiServer->dataConfirmationToTunnel(frame); + + frame.messageCode(L_data_ind); + + // Send to local stack ( => cemiServer for potential other tunnel and network layer for routing) + frameReceived(frame); + +#ifdef KNX_TUNNELING + // TunnelOpti + // Optimize performance when receiving unicast data over tunnel wich is not meant to be used on the physical TP line + // dont send to knx when + // frame is individual adressed AND + // destionation == PA of Tunnel-Server OR + // destination == PA of a Tunnel OR (TODO) + // destination is not the TP/secondary line/segment but IP/primary (TODO) + + if (frame.addressType() == AddressType::IndividualAddress) + { + if (frame.destinationAddress() == _deviceObject.individualAddress()) + return; + + if (isRoutedPA(frame.destinationAddress())) + return; + + if (isTunnelingPA(frame.destinationAddress())) + return; + } + +#endif + + // Send to KNX medium + sendFrame(frame); +} +#endif + +void DataLinkLayer::dataRequest(AckType ack, AddressType addrType, uint16_t destinationAddr, uint16_t sourceAddr, FrameFormat format, Priority priority, NPDU& npdu) +{ + // Normal data requests and broadcasts will always be transmitted as (domain) broadcast with domain address for open media (e.g. RF medium) + // The domain address "simulates" a closed medium (such as TP) on an open medium (such as RF or PL) + // See 3.2.5 p.22 + sendTelegram(npdu, ack, destinationAddr, addrType, sourceAddr, format, priority, Broadcast); +} + +void DataLinkLayer::systemBroadcastRequest(AckType ack, FrameFormat format, Priority priority, NPDU& npdu, uint16_t sourceAddr) +{ + // System Broadcast requests will always be transmitted as broadcast with KNX serial number for open media (e.g. RF medium) + // See 3.2.5 p.22 + sendTelegram(npdu, ack, 0, GroupAddress, sourceAddr, format, priority, SysBroadcast); +} + +void DataLinkLayer::dataConReceived(CemiFrame& frame, bool success) +{ + MessageCode backupMsgCode = frame.messageCode(); + frame.messageCode(L_data_con); + frame.confirm(success ? ConfirmNoError : ConfirmError); + AckType ack = frame.ack(); + AddressType addrType = frame.addressType(); + uint16_t destination = frame.destinationAddress(); + uint16_t source = frame.sourceAddress(); + FrameFormat type = frame.frameType(); + Priority priority = frame.priority(); + NPDU& npdu = frame.npdu(); + SystemBroadcast systemBroadcast = frame.systemBroadcast(); + +#ifdef USE_CEMI_SERVER + + // if the confirmation was caused by a tunnel request then + // do not send it to the local stack + if (frame.sourceAddress() == _cemiServer->clientAddress()) + { + // Stop processing here and do NOT send it the local network layer + return; + } + +#endif + + if (addrType == GroupAddress && destination == 0) + if (systemBroadcast == SysBroadcast) + _networkLayerEntity.systemBroadcastConfirm(ack, type, priority, source, npdu, success); + else + _networkLayerEntity.broadcastConfirm(ack, type, priority, source, npdu, success); + else + _networkLayerEntity.dataConfirm(ack, addrType, destination, type, priority, source, npdu, success); + + frame.messageCode(backupMsgCode); +} + +void DataLinkLayer::frameReceived(CemiFrame& frame) +{ + AckType ack = frame.ack(); + AddressType addrType = frame.addressType(); + uint16_t destination = frame.destinationAddress(); + uint16_t source = frame.sourceAddress(); + FrameFormat type = frame.frameType(); + Priority priority = frame.priority(); + NPDU& npdu = frame.npdu(); + uint16_t ownAddr = _deviceObject.individualAddress(); + SystemBroadcast systemBroadcast = frame.systemBroadcast(); + +#ifdef USE_CEMI_SERVER + // Do not send our own message back to the tunnel +#ifdef KNX_TUNNELING + + //we dont need to check it here + // send inbound frames to the tunnel if we are the secondary (TP) interface + if ( _networkLayerEntity.getEntityIndex() == 1) + _cemiServer->dataIndicationToTunnel(frame); + +#else + + if (frame.sourceAddress() != _cemiServer->clientAddress()) + { + _cemiServer->dataIndicationToTunnel(frame); + } + +#endif +#endif + + // print("Frame received destination: "); + // print(destination, 16); + // println(); + // print("frameReceived: frame valid? :"); + // println(npdu.frame().valid() ? "true" : "false"); + if (source == ownAddr) + _deviceObject.individualAddressDuplication(true); + + if (addrType == GroupAddress && destination == 0) + { + if (systemBroadcast == SysBroadcast) + _networkLayerEntity.systemBroadcastIndication(ack, type, npdu, priority, source); + else + _networkLayerEntity.broadcastIndication(ack, type, npdu, priority, source); + } + else + { + _networkLayerEntity.dataIndication(ack, addrType, destination, type, npdu, priority, source); + } +} + +bool DataLinkLayer::sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, uint16_t sourceAddr, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast, bool doNotRepeat) +{ + CemiFrame& frame = npdu.frame(); + // print("Send telegram frame valid ?: "); + // println(frame.valid()?"true":"false"); + frame.messageCode(L_data_ind); + frame.destinationAddress(destinationAddr); + frame.sourceAddress(sourceAddr); + frame.addressType(addrType); + frame.priority(priority); + frame.repetition(doNotRepeat ? NoRepitiion : RepetitionAllowed); + frame.systemBroadcast(systemBroadcast); + + if (npdu.octetCount() <= 15) + frame.frameType(StandardFrame); + else + frame.frameType(format); + + + if (!frame.valid()) + { + println("invalid frame"); + return false; + } + + // if (frame.npdu().octetCount() > 0) + // { + // _print("<- DLL "); + // frame.apdu().printPDU(); + // } + + bool sendTheFrame = true; + bool success = true; + +#ifdef KNX_TUNNELING + // TunnelOpti + // Optimize performance when sending unicast data over tunnel wich is not meant to be used on the physical TP line + // dont send to knx when + // a) we are the secondary interface (e.g. TP) AND + // b) destination == PA of a Tunnel (TODO) + + if (_networkLayerEntity.getEntityIndex() == 1 && addrType == AddressType::IndividualAddress) // don't send to tp if we are the secondary (TP) interface AND the destination is a tunnel-PA + { + if (isTunnelingPA(destinationAddr)) + sendTheFrame = false; + } + +#endif + + // The data link layer might be an open media link layer + // and will setup rfSerialOrDoA, rfInfo and rfLfn that we also + // have to send through the cEMI server tunnel + // Thus, reuse the modified cEMI frame as "frame" is only passed by reference here! + if (sendTheFrame) + success = sendFrame(frame); + +#ifdef USE_CEMI_SERVER + CemiFrame tmpFrame(frame.data(), frame.totalLenght()); + // We can just copy the pointer for rfSerialOrDoA as sendFrame() sets + // a pointer to const uint8_t data in either device object (serial) or + // RF medium object (domain address) +#ifdef USE_RF + tmpFrame.rfSerialOrDoA(frame.rfSerialOrDoA()); + tmpFrame.rfInfo(frame.rfInfo()); + tmpFrame.rfLfn(frame.rfLfn()); +#endif + tmpFrame.confirm(ConfirmNoError); + + if (_networkLayerEntity.getEntityIndex() == 1) // only send to tunnel if we are the secondary (TP) interface + _cemiServer->dataIndicationToTunnel(tmpFrame); + +#endif + + return success; +} + +uint8_t* DataLinkLayer::frameData(CemiFrame& frame) +{ + return frame._data; +} + +#ifdef KNX_TUNNELING +bool DataLinkLayer::isTunnelingPA(uint16_t pa) +{ + uint8_t numAddresses = 0; + uint16_t* addresses = _ipParameters->additionalIndivualAddresses(numAddresses); + + for (uint8_t i = 0; i < numAddresses; i++) + { + if (pa == addresses[i]) + return true; + } + + return false; +} + +bool DataLinkLayer::isRoutedPA(uint16_t pa) +{ + uint16_t ownpa = _deviceObject.individualAddress(); + uint16_t own_sm; + + if ((ownpa & 0x0F00) == 0x0) + own_sm = 0xF000; + else + own_sm = 0xFF00; + + return (pa & own_sm) != ownpa; +} +#endif + diff --git a/components/knx/src/knx/data_link_layer.h b/components/knx/src/knx/data_link_layer.h new file mode 100644 index 0000000..380b12c --- /dev/null +++ b/components/knx/src/knx/data_link_layer.h @@ -0,0 +1,77 @@ +#pragma once + +#include "config.h" + +#include +#include "device_object.h" +#include "knx_types.h" +#include "network_layer_entity.h" +#ifdef KNX_TUNNELING + #include "ip_parameter_object.h" +#endif +#include "cemi_server.h" +#include "bau.h" + +class Platform; + +typedef void (*ActivityCallback)(uint8_t info); + +class DataLinkLayerCallbacks +{ + protected: + ActivityCallback _activityCallback = nullptr; + public: + virtual ~DataLinkLayerCallbacks() = default; + virtual void activity(uint8_t info); + virtual void setActivityCallback(ActivityCallback activityCallback); +}; + +class DataLinkLayer +{ + public: + DataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, + Platform& platform); + +#ifdef USE_CEMI_SERVER + // from tunnel + void cemiServer(CemiServer& cemiServer); + void dataRequestFromTunnel(CemiFrame& frame); +#ifdef KNX_TUNNELING + virtual void dataRequestToTunnel(CemiFrame& frame); + virtual void dataConfirmationToTunnel(CemiFrame& frame); + virtual void dataIndicationToTunnel(CemiFrame& frame); + virtual bool isTunnelAddress(uint16_t addr); + void ipParameterObject(IpParameterObject* object); +#endif +#endif + + // from network layer + void dataRequest(AckType ack, AddressType addrType, uint16_t destinationAddr, uint16_t sourceAddr, FrameFormat format, + Priority priority, NPDU& npdu); + void systemBroadcastRequest(AckType ack, FrameFormat format, Priority priority, NPDU& npdu, uint16_t sourceAddr); + virtual void loop() = 0; + virtual void enabled(bool value) = 0; + virtual bool enabled() const = 0; + virtual DptMedium mediumType() const = 0; + + protected: + void frameReceived(CemiFrame& frame); + void dataConReceived(CemiFrame& frame, bool success); + bool sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, uint16_t sourceAddr, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast, bool doNotRepeat = false); + virtual bool sendFrame(CemiFrame& frame) = 0; + uint8_t* frameData(CemiFrame& frame); + DeviceObject& _deviceObject; + NetworkLayerEntity& _networkLayerEntity; + Platform& _platform; +#ifdef USE_CEMI_SERVER + CemiServer* _cemiServer; +#endif +#ifdef KNX_ACTIVITYCALLBACK + uint8_t _netIndex = 0; +#endif +#ifdef KNX_TUNNELING + bool isTunnelingPA(uint16_t pa); + bool isRoutedPA(uint16_t pa); + IpParameterObject* _ipParameters; +#endif +}; diff --git a/components/knx/src/knx/data_property.cpp b/components/knx/src/knx/data_property.cpp new file mode 100644 index 0000000..f2279b4 --- /dev/null +++ b/components/knx/src/knx/data_property.cpp @@ -0,0 +1,168 @@ +#include "data_property.h" +#include "bits.h" + +#include + +uint8_t DataProperty::read(uint16_t start, uint8_t count, uint8_t* data) const +{ + if (start == 0) + { + pushWord(_currentElements, data); + return 1; + } + + if (count == 0 || _currentElements == 0 || start > _currentElements || count > _currentElements - start + 1) + return 0; + + + // we start counting with zero + start -= 1; + + // data is already big enough to hold the data + memcpy(data, _data + (start * ElementSize()), count * ElementSize()); + + return count; +} + +uint8_t DataProperty::write(uint16_t start, uint8_t count, const uint8_t* data) +{ + if (count == 0 || start > _maxElements || start + count > _maxElements + 1) + return 0; + + if (start == 0) + { + if (count == 1 && data[0] == 0 && data[1] == 0) + { + // reset _data + _currentElements = 0; + + if (_data) + { + delete[] _data; + _data = nullptr; + } + + return 1; + } + else + return 0; + } + + // we start counting with zero + start -= 1; + + if (start + count > _currentElements) + { + // reallocate memory for _data + uint8_t* oldData = _data; + size_t oldDataSize = _currentElements * ElementSize(); + + size_t newDataSize = (start + count) * ElementSize(); + _data = new uint8_t[newDataSize]; + memset(_data, 0, newDataSize); + + if (oldData != nullptr) + { + memcpy(_data, oldData, oldDataSize); + delete[] oldData; + } + + _currentElements = start + count; + } + + memcpy(_data + (start * ElementSize()), data, count * ElementSize()); + + return count; +} + +DataProperty::DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, + uint16_t maxElements, uint8_t access) + : Property(id, writeEnable, type, maxElements, access) +{} + +DataProperty::~DataProperty() +{ + if (_data) + delete[] _data; +} + +DataProperty::DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, + uint16_t maxElements, uint8_t access, uint16_t value) + : Property(id, writeEnable, type, maxElements, access) +{ + Property::write(value); +} + +DataProperty::DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, + uint16_t maxElements, uint8_t access, uint32_t value) + : Property(id, writeEnable, type, maxElements, access) +{ + Property::write(value); +} + +DataProperty::DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, + uint16_t maxElements, uint8_t access, uint8_t value) + : Property(id, writeEnable, type, maxElements, access) +{ + Property::write(value); +} + +DataProperty::DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, + uint16_t maxElements, uint8_t access, const uint8_t* value) + : Property(id, writeEnable, type, maxElements, access) +{ + Property::write(value); +} + +uint16_t DataProperty::saveSize() +{ + return sizeof(_currentElements) + _maxElements * ElementSize(); +} + + +const uint8_t* DataProperty::restore(const uint8_t* buffer) +{ + uint16_t elements = 0; + buffer = popWord(elements, buffer); + + if (elements != _currentElements) + { + if (_data != nullptr) + delete[] _data; + + _data = new uint8_t[elements * ElementSize()]; + _currentElements = elements; + } + + if (elements > 0) + buffer = popByteArray(_data, elements * ElementSize(), buffer); + + return buffer; +} + + +uint8_t* DataProperty::save(uint8_t* buffer) +{ + buffer = pushWord(_currentElements, buffer); + + if (_currentElements > 0) + buffer = pushByteArray(_data, _currentElements * ElementSize(), buffer); + + return buffer; +} + + +const uint8_t* DataProperty::data() +{ + return _data; +} + +const uint8_t* DataProperty::data(uint16_t elementIndex) +{ + if ((elementIndex == 0) || (elementIndex > _currentElements)) + return nullptr; + + elementIndex -= 1; // Starting from 0 + uint16_t offset = elementIndex * ElementSize(); + return _data + offset; +} diff --git a/components/knx/src/knx/data_property.h b/components/knx/src/knx/data_property.h new file mode 100644 index 0000000..f1cfca6 --- /dev/null +++ b/components/knx/src/knx/data_property.h @@ -0,0 +1,25 @@ +#pragma once + +#include "property.h" + +class DataProperty : public Property +{ + public: + DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, uint8_t access); + DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, uint8_t access, uint8_t value); + DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, uint8_t access, uint16_t value); + DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, uint8_t access, uint32_t value); + DataProperty(PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, uint8_t access, const uint8_t* value); + ~DataProperty() override; + uint8_t read(uint16_t start, uint8_t count, uint8_t* data) const override; + uint8_t write(uint16_t start, uint8_t count, const uint8_t* data) override; + uint8_t* save(uint8_t* buffer) override; + const uint8_t* restore(const uint8_t* buffer) override; + uint16_t saveSize() override; + const uint8_t* data(); + const uint8_t* data(uint16_t elementIndex); + + private: + uint16_t _currentElements = 0; + uint8_t* _data = nullptr; +}; diff --git a/components/knx/src/knx/datapoint_types.cpp b/components/knx/src/knx/datapoint_types.cpp new file mode 100644 index 0000000..b757d0f --- /dev/null +++ b/components/knx/src/knx/datapoint_types.cpp @@ -0,0 +1,64 @@ +/* + * datapoint_types.h - Conversion functions for datapoint types. + * + * Copyright (c) 2014 Stefan Taferner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + */ + +#include "datapoint_types.h" +#include + +// Sign for a negative DPT9 float value +#define DPT_FLOAT_NEG_SIGN 0x8000 + + +uint16_t dptToFloat(int32_t value) +{ + uint16_t exp = 0; + + if (value < -67108864 || value > 67076096) + return 0x7fff; + + if (value < 0) + { + while (value < -2048) + { + value >>= 1; + ++exp; + } + + return DPT_FLOAT_NEG_SIGN | (((int32_t) value) & 2047) | (exp << 11); + } + else + { + while (value > 2047) + { + value >>= 1; + ++exp; + } + + return value | (exp << 11); + } +} + +int32_t dptFromFloat(uint16_t dptValue) +{ + uint16_t exp = (dptValue >> 11) & 15; + int32_t value; + + if (dptValue == 0x7fff) + return INVALID_DPT_FLOAT; + + if (dptValue >= 0x8000) + value = dptValue | (-1L & ~2047); + else + value = dptValue & 2047; + + for (; exp; --exp) + value <<= 1; + + return value; +} diff --git a/components/knx/src/knx/datapoint_types.h b/components/knx/src/knx/datapoint_types.h new file mode 100644 index 0000000..1d4ff09 --- /dev/null +++ b/components/knx/src/knx/datapoint_types.h @@ -0,0 +1,37 @@ +/* + * datapoint_types.h - Conversion functions for datapoint types. + * + * Copyright (c) 2014 Stefan Taferner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + */ + +#pragma once +#include + +/** + * An invalid 2 uint8_t float (DPT9/EIS5). + * To be used for dptToFloat() and dptFromFloat(). + */ +#define INVALID_DPT_FLOAT 2147483647U + +/** + * Convert a value from uint32_t to 2 uint8_t float (DPT9/EIS5). The possible range + * of the values is -67108864 to 67076096. + * + * @param value - the value to convert. + * Use INVALID_DPT_FLOAT for the DPT9 "invalid data" value. + * @return The 2 uint8_t float (DPT9/EIS5). + */ +uint16_t dptToFloat(int32_t value); + +/** + * Convert a value from 2 uint8_t float (DPT9/EIS5) to integer. + * + * @param dptValue - the 2 uint8_t float (DPT9/EIS5) to convert + * @return The value as integer, or INVALID_DPT_FLOAT for the + * DPT9 "invalid data" value. + */ +int32_t dptFromFloat(uint16_t dptValue); \ No newline at end of file diff --git a/components/knx/src/knx/device_object.cpp b/components/knx/src/knx/device_object.cpp new file mode 100644 index 0000000..b6138f7 --- /dev/null +++ b/components/knx/src/knx/device_object.cpp @@ -0,0 +1,294 @@ +#include +#include "device_object.h" +#include "bits.h" +#include "data_property.h" +#include "callback_property.h" +#include "config.h" + +#define LEN_KNX_SERIAL 6 + +DeviceObject::DeviceObject() +{ + // Default to KNXA (0xFA) + // Note: ETS does not accept a SN 00FA00000000 in data secure mode. + // ETS says that 00FA00000000 looks "suspicious" in the log file. + uint8_t serialNumber[] = {0x00, 0xFA, 0x01, 0x02, 0x03, 0x04}; + uint8_t hardwareType[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + Property* properties[] = + { + new DataProperty(PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_DEVICE), + new DataProperty(PID_SERIAL_NUMBER, false, PDT_GENERIC_06, 1, ReadLv3 | WriteLv0, serialNumber), + new CallbackProperty(this, PID_MANUFACTURER_ID, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, + [](DeviceObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + pushByteArray(io->propertyData(PID_SERIAL_NUMBER), 2, data); + return 1; + }), + new DataProperty(PID_DEVICE_CONTROL, true, PDT_BITSET8, 1, ReadLv3 | WriteLv3, (uint8_t)0), + new DataProperty(PID_ORDER_INFO, false, PDT_GENERIC_10, 1, ReadLv3 | WriteLv0), + new DataProperty(PID_VERSION, false, PDT_VERSION, 1, ReadLv3 | WriteLv0, (uint16_t)3), + new DataProperty(PID_ROUTING_COUNT, true, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv3, (uint8_t)(6 << 4)), + new CallbackProperty(this, PID_PROG_MODE, true, PDT_BITSET8, 1, ReadLv3 | WriteLv3, + [](DeviceObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + *data = io->_prgMode; + return 1; + }, + [](DeviceObject * io, uint16_t start, uint8_t count, const uint8_t* data) -> uint8_t + { + if (start == 0) + return 1; + + io->_prgMode = *data; + return 1; + }), + new DataProperty(PID_MAX_APDU_LENGTH, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)254), + new CallbackProperty(this, PID_SUBNET_ADDR, false, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv0, + [](DeviceObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + *data = ((io->_ownAddress >> 8) & 0xff); + + return 1; + }), + new CallbackProperty(this, PID_DEVICE_ADDR, false, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv0, + [](DeviceObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + *data = (io->_ownAddress & 0xff); + return 1; + }), + new DataProperty(PID_IO_LIST, false, PDT_UNSIGNED_INT, 8, ReadLv3 | WriteLv0), + new DataProperty(PID_HARDWARE_TYPE, true, PDT_GENERIC_06, 1, ReadLv3 | WriteLv3, hardwareType), + new DataProperty(PID_DEVICE_DESCRIPTOR, false, PDT_GENERIC_02, 1, ReadLv3 | WriteLv0), +#ifdef USE_RF + new DataProperty(PID_RF_DOMAIN_ADDRESS_CEMI_SERVER, true, PDT_GENERIC_06, 1, ReadLv3 | WriteLv3), +#endif + }; + initializeProperties(sizeof(properties), properties); +} + +uint8_t* DeviceObject::save(uint8_t* buffer) +{ + buffer = pushWord(_ownAddress, buffer); + return InterfaceObject::save(buffer); +} + +const uint8_t* DeviceObject::restore(const uint8_t* buffer) +{ + buffer = popWord(_ownAddress, buffer); + return InterfaceObject::restore(buffer); +} + +uint16_t DeviceObject::saveSize() +{ + return 2 + InterfaceObject::saveSize(); +} + +uint16_t DeviceObject::individualAddress() +{ + return _ownAddress; +} + +void DeviceObject::individualAddress(uint16_t value) +{ + _ownAddress = value; +} + +#define USER_STOPPED 0x1 +#define OWN_ADDR_DUPL 0x2 +#define VERIFY_MODE 0x4 +#define SAFE_STATE 0x8 + + +void DeviceObject::individualAddressDuplication(bool value) +{ + Property* prop = property(PID_DEVICE_CONTROL); + uint8_t data; + prop->read(data); + + if (value) + data |= OWN_ADDR_DUPL; + else + data &= ~OWN_ADDR_DUPL; + + prop->write(data); +} + +bool DeviceObject::verifyMode() +{ + Property* prop = property(PID_DEVICE_CONTROL); + uint8_t data; + prop->read(data); + return (data & VERIFY_MODE) > 0; +} + +void DeviceObject::verifyMode(bool value) +{ + Property* prop = property(PID_DEVICE_CONTROL); + uint8_t data; + prop->read(data); + + if (value) + data |= VERIFY_MODE; + else + data &= ~VERIFY_MODE; + + prop->write(data); +} + +bool DeviceObject::progMode() +{ + return _prgMode == 1; +} + +void DeviceObject::progMode(bool value) +{ + if (value) + _prgMode = 1; + else + _prgMode = 0; +} + +uint16_t DeviceObject::manufacturerId() +{ + uint16_t manufacturerId; + popWord(manufacturerId, propertyData(PID_SERIAL_NUMBER)); + return manufacturerId; +} + +void DeviceObject::manufacturerId(uint16_t value) +{ + uint8_t data[LEN_KNX_SERIAL]; + memcpy(data, propertyData(PID_SERIAL_NUMBER), LEN_KNX_SERIAL); + pushWord(value, data); + propertyValue(PID_SERIAL_NUMBER, data); +} + +uint32_t DeviceObject::bauNumber() +{ + uint32_t bauNumber; + popInt(bauNumber, propertyData(PID_SERIAL_NUMBER) + 2); + return bauNumber; +} + +void DeviceObject::bauNumber(uint32_t value) +{ + uint8_t data[LEN_KNX_SERIAL]; + memcpy(data, propertyData(PID_SERIAL_NUMBER), LEN_KNX_SERIAL); + pushInt(value, data + 2); + propertyValue(PID_SERIAL_NUMBER, data); +} + +const uint8_t* DeviceObject::orderNumber() +{ + DataProperty* prop = (DataProperty*)property(PID_ORDER_INFO); + return prop->data(); +} + +void DeviceObject::orderNumber(const uint8_t* value) +{ + Property* prop = property(PID_ORDER_INFO); + prop->write(value); +} + +const uint8_t* DeviceObject::hardwareType() +{ + DataProperty* prop = (DataProperty*)property(PID_HARDWARE_TYPE); + return prop->data(); +} + +void DeviceObject::hardwareType(const uint8_t* value) +{ + Property* prop = property(PID_HARDWARE_TYPE); + prop->write(value); +} + +uint16_t DeviceObject::version() +{ + Property* prop = property(PID_VERSION); + uint16_t value; + prop->read(value); + return value; +} + +void DeviceObject::version(uint16_t value) +{ + Property* prop = property(PID_VERSION); + prop->write(value); +} + +uint16_t DeviceObject::maskVersion() +{ + Property* prop = property(PID_DEVICE_DESCRIPTOR); + uint16_t value; + prop->read(value); + return value; +} + +void DeviceObject::maskVersion(uint16_t value) +{ + Property* prop = property(PID_DEVICE_DESCRIPTOR); + prop->write(value); +} + +uint16_t DeviceObject::maxApduLength() +{ + Property* prop = property(PID_MAX_APDU_LENGTH); + uint16_t value; + prop->read(value); + return value; +} + +void DeviceObject::maxApduLength(uint16_t value) +{ + Property* prop = property(PID_MAX_APDU_LENGTH); + prop->write(value); +} + +const uint8_t* DeviceObject::rfDomainAddress() +{ + DataProperty* prop = (DataProperty*)property(PID_RF_DOMAIN_ADDRESS_CEMI_SERVER); + return prop->data(); +} + +void DeviceObject::rfDomainAddress(uint8_t* value) +{ + Property* prop = property(PID_RF_DOMAIN_ADDRESS_CEMI_SERVER); + prop->write(value); +} + +uint8_t DeviceObject::defaultHopCount() +{ + Property* prop = property(PID_ROUTING_COUNT); + uint8_t value; + prop->read(value); + return (value >> 4) & 0x07; +} diff --git a/components/knx/src/knx/device_object.h b/components/knx/src/knx/device_object.h new file mode 100644 index 0000000..9a3ad4d --- /dev/null +++ b/components/knx/src/knx/device_object.h @@ -0,0 +1,51 @@ +#pragma once + +#include "interface_object.h" + +#define LEN_HARDWARE_TYPE 6 + +class DeviceObject: public InterfaceObject +{ +public: + // increase this version anytime DeviceObject-API changes + // the following value represents the serialized representation of DeviceObject. + const uint16_t apiVersion = 1; + + DeviceObject(); + uint8_t* save(uint8_t* buffer) override; + const uint8_t* restore(const uint8_t* buffer) override; + uint16_t saveSize() override; + + uint16_t individualAddress(); + void individualAddress(uint16_t value); + + void individualAddressDuplication(bool value); + bool verifyMode(); + void verifyMode(bool value); + bool progMode(); + void progMode(bool value); + uint16_t manufacturerId(); + void manufacturerId(uint16_t value); + uint32_t bauNumber(); + void bauNumber(uint32_t value); + const uint8_t* orderNumber(); + void orderNumber(const uint8_t* value); + const uint8_t* hardwareType(); + void hardwareType(const uint8_t* value); + uint16_t version(); + void version(uint16_t value); + uint16_t maskVersion(); + void maskVersion(uint16_t value); + uint16_t maxApduLength(); + void maxApduLength(uint16_t value); + const uint8_t* rfDomainAddress(); + void rfDomainAddress(uint8_t* value); + uint8_t defaultHopCount(); + private: + uint8_t _prgMode = 0; +#if MASK_VERSION == 0x091A || MASK_VERSION == 0x2920 + uint16_t _ownAddress = 0xFF00; // 15.15.0; couplers have 15.15.0 as default PA +#else + uint16_t _ownAddress = 0xFFFF; // 15.15.255; +#endif +}; diff --git a/components/knx/src/knx/dpt.cpp b/components/knx/src/knx/dpt.cpp new file mode 100644 index 0000000..c8791ce --- /dev/null +++ b/components/knx/src/knx/dpt.cpp @@ -0,0 +1,23 @@ +#include "dpt.h" + +#include "bits.h" + +Dpt::Dpt() +{} + +Dpt::Dpt(short mainGroup, short subGroup, short index /* = 0 */) + : mainGroup(mainGroup), subGroup(subGroup), index(index) +{ + if (subGroup == 0 && (mainGroup < 14 || mainGroup > 16)) + println("WARNING: You used an invalid Dpt *.0"); +} + +bool Dpt::operator==(const Dpt& other) const +{ + return other.mainGroup == mainGroup && other.subGroup == subGroup && other.index == index; +} + +bool Dpt::operator!=(const Dpt& other) const +{ + return !(other == *this); +} diff --git a/components/knx/src/knx/dpt.h b/components/knx/src/knx/dpt.h new file mode 100644 index 0000000..07b7496 --- /dev/null +++ b/components/knx/src/knx/dpt.h @@ -0,0 +1,373 @@ +#pragma once + +#define DPT_Switch Dpt(1, 1) +#define DPT_Bool Dpt(1, 2) +#define DPT_Enable Dpt(1, 3) +#define DPT_Ramp Dpt(1, 4) +#define DPT_Alarm Dpt(1, 5) +#define DPT_BinaryValue Dpt(1, 6) +#define DPT_Step Dpt(1, 7) +#define DPT_UpDown Dpt(1, 8) +#define DPT_OpenClose Dpt(1, 9) +#define DPT_Start Dpt(1, 10) +#define DPT_State Dpt(1, 11) +#define DPT_Invert Dpt(1, 12) +#define DPT_DimSendStyle Dpt(1, 13) +#define DPT_InputSource Dpt(1, 14) +#define DPT_Reset Dpt(1, 15) +#define DPT_Ack Dpt(1, 16) +#define DPT_Trigger Dpt(1, 17) +#define DPT_Occupancy Dpt(1, 18) +#define DPT_Window_Door Dpt(1, 19) +#define DPT_LogicalFunction Dpt(1, 21) +#define DPT_Scene_AB Dpt(1, 22) +#define DPT_ShutterBlinds_Mode Dpt(1, 23) +#define DPT_Heat_Cool Dpt(1, 100) +#define DPT_Switch_Control Dpt(2, 1) +#define DPT_Bool_Control Dpt(2, 2) +#define DPT_Enable_Control Dpt(2, 3) +#define DPT_Ramp_Control Dpt(2, 4) +#define DPT_Alarm_Control Dpt(2, 5) +#define DPT_BinaryValue_Control Dpt(2, 6) +#define DPT_Step_Control Dpt(2, 7) +#define DPT_Direction1_Control Dpt(2, 8) +#define DPT_Direction2_Control Dpt(2, 9) +#define DPT_Start_Control Dpt(2, 10) +#define DPT_State_Control Dpt(2, 11) +#define DPT_Invert_Control Dpt(2, 12) +#define DPT_Control_Dimming Dpt(3, 7) +#define DPT_Control_Blinds Dpt(3, 8) +#define DPT_Char_ASCII Dpt(4, 1) +#define DPT_Char_8859_1 Dpt(4, 2) +#define DPT_Scaling Dpt(5, 1) +#define DPT_Angle Dpt(5, 3) +#define DPT_Percent_U8 Dpt(5, 4) +#define DPT_DecimalFactor Dpt(5, 5) +#define DPT_Tariff Dpt(5, 6) +#define DPT_Value_1_Ucount Dpt(5, 10) +#define DPT_Percent_V8 Dpt(6, 1) +#define DPT_Value_1_Count Dpt(6, 10) +#define DPT_Status_Mode3 Dpt(6, 20) +#define DPT_Value_2_Ucount Dpt(7, 1) +#define DPT_TimePeriodMsec Dpt(7, 2) +#define DPT_TimePeriod10MSec Dpt(7, 3) +#define DPT_TimePeriod100MSec Dpt(7, 4) +#define DPT_TimePeriodSec Dpt(7, 5) +#define DPT_TimePeriodMin Dpt(7, 6) +#define DPT_TimePeriodHrs Dpt(7, 7) +#define DPT_PropDataType Dpt(7, 10) +#define DPT_Length_mm Dpt(7, 11) +#define DPT_UElCurrentmA Dpt(7, 12) +#define DPT_Brightness Dpt(7, 13) +#define DPT_Value_2_Count Dpt(8, 1) +#define DPT_DeltaTimeMsec Dpt(8, 2) +#define DPT_DeltaTime10MSec Dpt(8, 3) +#define DPT_DeltaTime100MSec Dpt(8, 4) +#define DPT_DeltaTimeSec Dpt(8, 5) +#define DPT_DeltaTimeMin Dpt(8, 6) +#define DPT_DeltaTimeHrs Dpt(8, 7) +#define DPT_Percent_V16 Dpt(8, 10) +#define DPT_Rotation_Angle Dpt(8, 11) +#define DPT_Value_Temp Dpt(9, 1) +#define DPT_Value_Tempd Dpt(9, 2) +#define DPT_Value_Tempa Dpt(9, 3) +#define DPT_Value_Lux Dpt(9, 4) +#define DPT_Value_Wsp Dpt(9, 5) +#define DPT_Value_Pres Dpt(9, 6) +#define DPT_Value_Humidity Dpt(9, 7) +#define DPT_Value_AirQuality Dpt(9, 8) +#define DPT_Value_Time1 Dpt(9, 10) +#define DPT_Value_Time2 Dpt(9, 11) +#define DPT_Value_Volt Dpt(9, 20) +#define DPT_Value_Curr Dpt(9, 21) +#define DPT_PowerDensity Dpt(9, 22) +#define DPT_KelvinPerPercent Dpt(9, 23) +#define DPT_Power Dpt(9, 24) +#define DPT_Value_Volume_Flow Dpt(9, 25) +#define DPT_Rain_Amount Dpt(9, 26) +#define DPT_Value_Temp_F Dpt(9, 27) +#define DPT_Value_Wsp_kmh Dpt(9, 28) +#define DPT_TimeOfDay Dpt(10, 1, 1) +#define DPT_Date Dpt(11, 1) +#define DPT_Value_4_Ucount Dpt(12, 1) +#define DPT_Value_4_Count Dpt(13, 1) +#define DPT_FlowRate_m3_per_h Dpt(13, 2) +#define DPT_ActiveEnergy Dpt(13, 10) +#define DPT_ApparantEnergy Dpt(13, 11) +#define DPT_ReactiveEnergy Dpt(13, 12) +#define DPT_ActiveEnergy_kWh Dpt(13, 13) +#define DPT_ApparantEnergy_kVAh Dpt(13, 14) +#define DPT_ReactiveEnergy_kVARh Dpt(13, 15) +#define DPT_LongDeltaTimeSec Dpt(13, 100) +#define DPT_Value_Acceleration Dpt(14, 0) +#define DPT_Value_Acceleration_Angular Dpt(14, 1) +#define DPT_Value_Activation_Energy Dpt(14, 2) +#define DPT_Value_Activity Dpt(14, 3) +#define DPT_Value_Mol Dpt(14, 4) +#define DPT_Value_Amplitude Dpt(14, 5) +#define DPT_Value_AngleRad Dpt(14, 6) +#define DPT_Value_AngleDeg Dpt(14, 7) +#define DPT_Value_Angular_Momentum Dpt(14, 8) +#define DPT_Value_Angular_Velocity Dpt(14, 9) +#define DPT_Value_Area Dpt(14, 10) +#define DPT_Value_Capacitance Dpt(14, 11) +#define DPT_Value_Charge_DensitySurface Dpt(14, 12) +#define DPT_Value_Charge_DensityVolume Dpt(14, 13) +#define DPT_Value_Compressibility Dpt(14, 14) +#define DPT_Value_Conductance Dpt(14, 15) +#define DPT_Value_Electrical_Conductivity Dpt(14, 16) +#define DPT_Value_Density Dpt(14, 17) +#define DPT_Value_Electric_Charge Dpt(14, 18) +#define DPT_Value_Electric_Current Dpt(14, 19) +#define DPT_Value_Electric_CurrentDensity Dpt(14, 20) +#define DPT_Value_Electric_DipoleMoment Dpt(14, 21) +#define DPT_Value_Electric_Displacement Dpt(14, 22) +#define DPT_Value_Electric_FieldStrength Dpt(14, 23) +#define DPT_Value_Electric_Flux Dpt(14, 24) +#define DPT_Value_Electric_FluxDensity Dpt(14, 25) +#define DPT_Value_Electric_Polarization Dpt(14, 26) +#define DPT_Value_Electric_Potential Dpt(14, 27) +#define DPT_Value_Electric_PotentialDifference Dpt(14, 28) +#define DPT_Value_ElectromagneticMoment Dpt(14, 29) +#define DPT_Value_Electromotive_Force Dpt(14, 30) +#define DPT_Value_Energy Dpt(14, 31) +#define DPT_Value_Force Dpt(14, 32) +#define DPT_Value_Frequency Dpt(14, 33) +#define DPT_Value_Angular_Frequency Dpt(14, 34) +#define DPT_Value_Heat_Capacity Dpt(14, 35) +#define DPT_Value_Heat_FlowRate Dpt(14, 36) +#define DPT_Value_Heat_Quantity Dpt(14, 37) +#define DPT_Value_Impedance Dpt(14, 38) +#define DPT_Value_Length Dpt(14, 39) +#define DPT_Value_Light_Quantity Dpt(14, 40) +#define DPT_Value_Luminance Dpt(14, 41) +#define DPT_Value_Luminous_Flux Dpt(14, 42) +#define DPT_Value_Luminous_Intensity Dpt(14, 43) +#define DPT_Value_Magnetic_FieldStrength Dpt(14, 44) +#define DPT_Value_Magnetic_Flux Dpt(14, 45) +#define DPT_Value_Magnetic_FluxDensity Dpt(14, 46) +#define DPT_Value_Magnetic_Moment Dpt(14, 47) +#define DPT_Value_Magnetic_Polarization Dpt(14, 48) +#define DPT_Value_Magnetization Dpt(14, 49) +#define DPT_Value_MagnetomotiveForce Dpt(14, 50) +#define DPT_Value_Mass Dpt(14, 51) +#define DPT_Value_MassFlux Dpt(14, 52) +#define DPT_Value_Momentum Dpt(14, 53) +#define DPT_Value_Phase_AngleRad Dpt(14, 54) +#define DPT_Value_Phase_AngleDeg Dpt(14, 55) +#define DPT_Value_Power Dpt(14, 56) +#define DPT_Value_Power_Factor Dpt(14, 57) +#define DPT_Value_Pressure Dpt(14, 58) +#define DPT_Value_Reactance Dpt(14, 59) +#define DPT_Value_Resistance Dpt(14, 60) +#define DPT_Value_Resistivity Dpt(14, 61) +#define DPT_Value_SelfInductance Dpt(14, 62) +#define DPT_Value_SolidAngle Dpt(14, 63) +#define DPT_Value_Sound_Intensity Dpt(14, 64) +#define DPT_Value_Speed Dpt(14, 65) +#define DPT_Value_Stress Dpt(14, 66) +#define DPT_Value_Surface_Tension Dpt(14, 67) +#define DPT_Value_Common_Temperature Dpt(14, 68) +#define DPT_Value_Absolute_Temperature Dpt(14, 69) +#define DPT_Value_TemperatureDifference Dpt(14, 70) +#define DPT_Value_Thermal_Capacity Dpt(14, 71) +#define DPT_Value_Thermal_Conductivity Dpt(14, 72) +#define DPT_Value_ThermoelectricPower Dpt(14, 73) +#define DPT_Value_Time Dpt(14, 74) +#define DPT_Value_Torque Dpt(14, 75) +#define DPT_Value_Volume Dpt(14, 76) +#define DPT_Value_Volume_Flux Dpt(14, 77) +#define DPT_Value_Weight Dpt(14, 78) +#define DPT_Value_Work Dpt(14, 79) +#define DPT_Value_ApparentPower Dpt(14, 80) +#define DPT_Access_Data Dpt(15, 0) +#define DPT_String_ASCII Dpt(16, 0) +#define DPT_String_8859_1 Dpt(16, 1) +#define DPT_SceneNumber Dpt(17, 1) +#define DPT_SceneControl Dpt(18, 1) +#define DPT_DateTime Dpt(19, 1) +#define DPT_SCLOMode Dpt(20, 1) +#define DPT_BuildingMode Dpt(20, 2) +#define DPT_OccMode Dpt(20, 3) +#define DPT_Priority Dpt(20, 4) +#define DPT_LightApplicationMode Dpt(20, 5) +#define DPT_ApplicationArea Dpt(20, 6) +#define DPT_AlarmClassType Dpt(20, 7) +#define DPT_PSUMode Dpt(20, 8) +#define DPT_ErrorClass_System Dpt(20, 11) +#define DPT_ErrorClass_HVAC Dpt(20, 12) +#define DPT_Time_Delay Dpt(20, 13) +#define DPT_Beaufort_Wind_Force_Scale Dpt(20, 14) +#define DPT_SensorSelect Dpt(20, 17) +#define DPT_ActuatorConnectType Dpt(20, 20) +#define DPT_FuelType Dpt(20, 100) +#define DPT_BurnerType Dpt(20, 101) +#define DPT_HVACMode Dpt(20, 102) +#define DPT_DHWMode Dpt(20, 103) +#define DPT_LoadPriority Dpt(20, 104) +#define DPT_HVACContrMode Dpt(20, 105) +#define DPT_HVACEmergMode Dpt(20, 106) +#define DPT_ChangeoverMode Dpt(20, 107) +#define DPT_ValveMode Dpt(20, 108) +#define DPT_DamperMode Dpt(20, 109) +#define DPT_HeaterMode Dpt(20, 110) +#define DPT_FanMode Dpt(20, 111) +#define DPT_MasterSlaveMode Dpt(20, 112) +#define DPT_StatusRoomSetp Dpt(20, 113) +#define DPT_ADAType Dpt(20, 120) +#define DPT_BackupMode Dpt(20, 121) +#define DPT_StartSynchronization Dpt(20, 122) +#define DPT_Behaviour_Lock_Unlock Dpt(20, 600) +#define DPT_Behaviour_Bus_Power_Up_Down Dpt(20, 601) +#define DPT_DALI_Fade_Time Dpt(20, 602) +#define DPT_BlinkingMode Dpt(20, 603) +#define DPT_LightControlMode Dpt(20, 604) +#define DPT_SwitchPBModel Dpt(20, 605) +#define DPT_PBAction Dpt(20, 606) +#define DPT_DimmPBModel Dpt(20, 607) +#define DPT_SwitchOnMode Dpt(20, 608) +#define DPT_LoadTypeSet Dpt(20, 609) +#define DPT_LoadTypeDetected Dpt(20, 610) +#define DPT_SABExceptBehaviour Dpt(20, 801) +#define DPT_SABBehaviour_Lock_Unlock Dpt(20, 802) +#define DPT_SSSBMode Dpt(20, 803) +#define DPT_BlindsControlMode Dpt(20, 804) +#define DPT_CommMode Dpt(20, 1000) +#define DPT_AddInfoTypes Dpt(20, 1001) +#define DPT_RF_ModeSelect Dpt(20, 1002) +#define DPT_RF_FilterSelect Dpt(20, 1003) +#define DPT_StatusGen Dpt(21, 1) +#define DPT_Device_Control Dpt(21, 2) +#define DPT_ForceSign Dpt(21, 100) +#define DPT_ForceSignCool Dpt(21, 101) +#define DPT_StatusRHC Dpt(21, 102) +#define DPT_StatusSDHWC Dpt(21, 103) +#define DPT_FuelTypeSet Dpt(21, 104) +#define DPT_StatusRCC Dpt(21, 105) +#define DPT_StatusAHU Dpt(21, 106) +#define DPT_LightActuatorErrorInfo Dpt(21, 601) +#define DPT_RF_ModeInfo Dpt(21, 1000) +#define DPT_RF_FilterInfo Dpt(21, 1001) +#define DPT_Channel_Activation_8 Dpt(21, 1010) +#define DPT_StatusDHWC Dpt(22, 100) +#define DPT_StatusRHCC Dpt(22, 101) +#define DPT_Media Dpt(22, 1000) +#define DPT_Channel_Activation_16 Dpt(22, 1010) +#define DPT_OnOff_Action Dpt(23, 1) +#define DPT_Alarm_Reaction Dpt(23, 2) +#define DPT_UpDown_Action Dpt(23, 3) +#define DPT_HVAC_PB_Action Dpt(23, 102) +#define DPT_VarString_8859_1 Dpt(24, 1) +#define DPT_DoubleNibble Dpt(25, 1000) +#define DPT_SceneInfo Dpt(26, 1) +#define DPT_CombinedInfoOnOff Dpt(27, 1) +#define DPT_UTF_8 Dpt(28, 1) +#define DPT_ActiveEnergy_V64 Dpt(29, 10) +#define DPT_ApparantEnergy_V64 Dpt(29, 11) +#define DPT_ReactiveEnergy_V64 Dpt(29, 12) +#define DPT_Channel_Activation_24 Dpt(30, 1010) +#define DPT_PB_Action_HVAC_Extended Dpt(31, 101) +#define DPT_Heat_Cool_Z Dpt(200, 100) +#define DPT_BinaryValue_Z Dpt(200, 101) +#define DPT_HVACMode_Z Dpt(201, 100) +#define DPT_DHWMode_Z Dpt(201, 102) +#define DPT_HVACContrMode_Z Dpt(201, 104) +#define DPT_EnablH_Cstage_Z Dpt(201, 105) +#define DPT_BuildingMode_Z Dpt(201, 107) +#define DPT_OccMode_Z Dpt(201, 108) +#define DPT_HVACEmergMode_Z Dpt(201, 109) +#define DPT_RelValue_Z Dpt(202, 1) +#define DPT_UCountValue8_Z Dpt(202, 2) +#define DPT_TimePeriodMsec_Z Dpt(203, 2) +#define DPT_TimePeriod10Msec_Z Dpt(203, 3) +#define DPT_TimePeriod100Msec_Z Dpt(203, 4) +#define DPT_TimePeriodSec_Z Dpt(203, 5) +#define DPT_TimePeriodMin_Z Dpt(203, 6) +#define DPT_TimePeriodHrs_Z Dpt(203, 7) +#define DPT_UFlowRateLiter_per_h_Z Dpt(203, 11) +#define DPT_UCountValue16_Z Dpt(203, 12) +#define DPT_UElCurrent_Z Dpt(203, 13) +#define DPT_PowerKW_Z Dpt(203, 14) +#define DPT_AtmPressureAbs_Z Dpt(203, 15) +#define DPT_PercentU16_Z Dpt(203, 17) +#define DPT_HVACAirQual_Z Dpt(203, 100) +#define DPT_WindSpeed_Z Dpt(203, 101) +#define DPT_SunIntensity_Z Dpt(203, 102) +#define DPT_HVACAirFlowAbs_Z Dpt(203, 104) +#define DPT_RelSignedValue_Z Dpt(204, 1) +#define DPT_DeltaTimeMsec_Z Dpt(205, 2) +#define DPT_DeltaTime10Msec_Z Dpt(205, 3) +#define DPT_DeltaTime100Msec_Z Dpt(205, 4) +#define DPT_DeltaTimeSec_Z Dpt(205, 5) +#define DPT_DeltaTimeMin_Z Dpt(205, 6) +#define DPT_DeltaTimeHrs_Z Dpt(205, 7) +#define DPT_Percent_V16_Z Dpt(205, 17) +#define DPT_TempHVACAbs_Z Dpt(205, 100) +#define DPT_TempHVACRel_Z Dpt(205, 101) +#define DPT_HVACAirFlowRel_Z Dpt(205, 102) +#define DPT_HVACModeNext Dpt(206, 100) +#define DPT_DHWModeNext Dpt(206, 102) +#define DPT_OccModeNext Dpt(206, 104) +#define DPT_BuildingModeNext Dpt(206, 105) +#define DPT_StatusBUC Dpt(207, 100) +#define DPT_LockSign Dpt(207, 101) +#define DPT_ValueDemBOC Dpt(207, 102) +#define DPT_ActPosDemAbs Dpt(207, 104) +#define DPT_StatusAct Dpt(207, 105) +#define DPT_StatusLightingActuator Dpt(207, 600) +#define DPT_StatusHPM Dpt(209, 100) +#define DPT_TempRoomDemAbs Dpt(209, 101) +#define DPT_StatusCPM Dpt(209, 102) +#define DPT_StatusWTC Dpt(209, 103) +#define DPT_TempFlowWaterDemAbs Dpt(210, 100) +#define DPT_EnergyDemWater Dpt(211, 100) +#define DPT_TempRoomSetpSetShift_3 Dpt(212, 100) +#define DPT_TempRoomSetpSet_3 Dpt(212, 101) +#define DPT_TempRoomSetpSet_4 Dpt(213, 100) +#define DPT_TempDHWSetpSet_4 Dpt(213, 101) +#define DPT_TempRoomSetpSetShift_4 Dpt(213, 102) +#define DPT_PowerFlowWaterDemHPM Dpt(214, 100) +#define DPT_PowerFlowWaterDemCPM Dpt(214, 101) +#define DPT_StatusBOC Dpt(215, 100) +#define DPT_StatusCC Dpt(215, 101) +#define DPT_SpecHeatProd Dpt(216, 100) +#define DPT_Version Dpt(217, 1) +#define DPT_VolumeLiter_Z Dpt(218, 1) +#define DPT_FlowRate_m3_per_h_Z Dpt(218, 2) +#define DPT_AlarmInfo Dpt(219, 1) +#define DPT_TempHVACAbsNext Dpt(220, 100) +#define DPT_SerNum Dpt(221, 1) +#define DPT_TempRoomSetpSetF16_3 Dpt(222, 100) +#define DPT_TempRoomSetpSetShiftF16_3 Dpt(222, 101) +#define DPT_EnergyDemAir Dpt(223, 100) +#define DPT_TempSupplyAirSetpSet Dpt(224, 100) +#define DPT_ScalingSpeed Dpt(225, 1) +#define DPT_Scaling_Step_Time Dpt(225, 2) +#define DPT_TariffNext Dpt(225, 3) +#define DPT_MeteringValue Dpt(229, 1) +#define DPT_MBus_Address Dpt(230, 1000) +#define DPT_Locale_ASCII Dpt(231, 1) +#define DPT_Colour_RGB Dpt(232, 600) +#define DPT_LanguageCodeAlpha2_ASCII Dpt(234, 1) +#define DPT_RegionCodeAlpha2_ASCII Dpt(234, 2) +#define DPT_Tariff_ActiveEnergy Dpt(235, 1) +#define DPT_Prioritised_Mode_Control Dpt(236, 1) +#define DPT_DALI_Control_Gear_Diagnostic Dpt(237, 600) +#define DPT_SceneConfig Dpt(238, 1) +#define DPT_DALI_Diagnostics Dpt(238, 600) +#define DPT_FlaggedScaling Dpt(239, 1) +#define DPT_CombinedPosition Dpt(240, 800) +#define DPT_StatusSAB Dpt(241, 800) +#define DPT_Colour_RGBW Dpt(251, 600) + +class Dpt +{ + public: + Dpt(); + Dpt(short mainGroup, short subGroup, short index = 0); + unsigned short mainGroup; + unsigned short subGroup; + unsigned short index; + bool operator==(const Dpt& other) const; + bool operator!=(const Dpt& other) const; +}; diff --git a/components/knx/src/knx/dptconvert.cpp b/components/knx/src/knx/dptconvert.cpp new file mode 100644 index 0000000..1a3b46a --- /dev/null +++ b/components/knx/src/knx/dptconvert.cpp @@ -0,0 +1,2008 @@ +#include "dptconvert.h" +#include "bits.h" +#include +#include +#include + +#define ASSERT_PAYLOAD(x) \ + if (payload_length != (x)) \ + return false +#define ENSURE_PAYLOAD(x) + + +bool KNX_Decode_Value(uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + if (payload_length > 0) + { + // DPT 1.* - Binary + if (datatype.mainGroup == 1 && datatype.subGroup >= 1 && datatype.subGroup <= 23 && datatype.subGroup != 20 && !datatype.index) + return busValueToBinary(payload, payload_length, datatype, value); + + // DPT 2.* - Binary Control + if (datatype.mainGroup == 2 && datatype.subGroup >= 1 && datatype.subGroup <= 12 && datatype.index <= 1) + return busValueToBinaryControl(payload, payload_length, datatype, value); + + // DPT 3.* - Step Control + if (datatype.mainGroup == 3 && datatype.subGroup >= 7 && datatype.subGroup <= 8 && datatype.index <= 1) + return busValueToStepControl(payload, payload_length, datatype, value); + + // DPT 4.* - Character + if (datatype.mainGroup == 4 && datatype.subGroup >= 1 && datatype.subGroup <= 2 && !datatype.index) + return busValueToCharacter(payload, payload_length, datatype, value); + + // DPT 5.* - Unsigned 8 Bit Integer + if (datatype.mainGroup == 5 && ((datatype.subGroup >= 1 && datatype.subGroup <= 6 && datatype.subGroup != 2) || datatype.subGroup == 10) && !datatype.index) + return busValueToUnsigned8(payload, payload_length, datatype, value); + + // DPT 6.001/6.010 - Signed 8 Bit Integer + if (datatype.mainGroup == 6 && (datatype.subGroup == 1 || datatype.subGroup == 10) && !datatype.index) + return busValueToSigned8(payload, payload_length, datatype, value); + + // DPT 6.020 - Status with Mode + if (datatype.mainGroup == 6 && datatype.subGroup == 20 && datatype.index <= 5) + return busValueToStatusAndMode(payload, payload_length, datatype, value); + + // DPT 7.001/7.010/7.011/7.012/7.013/7.600 - Unsigned 16 Bit Integer + if (datatype.mainGroup == 7 && (datatype.subGroup == 1 || (datatype.subGroup >= 10 && datatype.subGroup <= 13) || (datatype.subGroup == 600)) && !datatype.index) + return busValueToUnsigned16(payload, payload_length, datatype, value); + + // DPT 7.002-DPT 7.007 - Time Period + if (datatype.mainGroup == 7 && datatype.subGroup >= 2 && datatype.subGroup <= 7 && !datatype.index) + return busValueToTimePeriod(payload, payload_length, datatype, value); + + // DPT 8.001/8.010/8.011 - Signed 16 Bit Integer + if (datatype.mainGroup == 8 && (datatype.subGroup == 1 || datatype.subGroup == 10 || datatype.subGroup == 11) && !datatype.index) + return busValueToSigned16(payload, payload_length, datatype, value); + + // DPT 8.002-DPT 8.007 - Time Delta + if (datatype.mainGroup == 8 && datatype.subGroup >= 2 && datatype.subGroup <= 7 && !datatype.index) + return busValueToTimeDelta(payload, payload_length, datatype, value); + + // DPT 9.* - 16 Bit Float + if (datatype.mainGroup == 9 && ((datatype.subGroup >= 1 && datatype.subGroup <= 11) || (datatype.subGroup >= 20 && datatype.subGroup <= 29)) && !datatype.index) + return busValueToFloat16(payload, payload_length, datatype, value); + + // DPT 10.* - Time and Weekday + if (datatype.mainGroup == 10 && datatype.subGroup == 1 && datatype.index <= 1) + return busValueToTime(payload, payload_length, datatype, value); + + // DPT 11.* - Date + if (datatype.mainGroup == 11 && datatype.subGroup == 1 && !datatype.index) + return busValueToDate(payload, payload_length, datatype, value); + + // DPT 12.* - Unsigned 32 Bit Integer + if (datatype.mainGroup == 12 && datatype.subGroup == 1 && !datatype.index) + return busValueToUnsigned32(payload, payload_length, datatype, value); + + // DPT 13.001/13.002/13.010-13.015 - Signed 32 Bit Integer + if (datatype.mainGroup == 13 && (datatype.subGroup == 1 || datatype.subGroup == 2 || (datatype.subGroup >= 10 && datatype.subGroup <= 15)) && !datatype.index) + return busValueToSigned32(payload, payload_length, datatype, value); + + // DPT 13.100 - Long Time Period + if (datatype.mainGroup == 13 && datatype.subGroup == 100 && !datatype.index) + return busValueToLongTimePeriod(payload, payload_length, datatype, value); + + // DPT 14.* - 32 Bit Float + if (datatype.mainGroup == 14 && datatype.subGroup <= 79 && !datatype.index) + return busValueToFloat32(payload, payload_length, datatype, value); + + // DPT 15.* - Access Data + if (datatype.mainGroup == 15 && !datatype.subGroup && datatype.index <= 5) + return busValueToAccess(payload, payload_length, datatype, value); + + // DPT 16.* - String + if (datatype.mainGroup == 16 && datatype.subGroup <= 1 && !datatype.index) + return busValueToString(payload, payload_length, datatype, value); + + // DPT 17.* - Scene Number + if (datatype.mainGroup == 17 && datatype.subGroup == 1 && !datatype.index) + return busValueToScene(payload, payload_length, datatype, value); + + // DPT 18.* - Scene Control + if (datatype.mainGroup == 18 && datatype.subGroup == 1 && datatype.index <= 1) + return busValueToSceneControl(payload, payload_length, datatype, value); + + // DPT 19.* - Date and Time + if (datatype.mainGroup == 19 && datatype.subGroup == 1 && (datatype.index <= 3 || datatype.index == 9 || datatype.index == 10)) + return busValueToDateTime(payload, payload_length, datatype, value); + + // DPT 20.* - HVAC Control mode Unsigned 8 Bit Integer + if (datatype.mainGroup == 20 && !datatype.index) + return busValueToUnsigned8(payload, payload_length, datatype, value); + + // DPT 26.* - Scene Info + if (datatype.mainGroup == 26 && datatype.subGroup == 1 && datatype.index <= 1) + return busValueToSceneInfo(payload, payload_length, datatype, value); + + // DPT 27.001 - 32 Bit field + if (datatype.mainGroup == 27 && datatype.subGroup == 1 && !datatype.index) + return busValueToSigned32(payload, payload_length, datatype, value); + + // DPT 28.* - Unicode String + if (datatype.mainGroup == 28 && datatype.subGroup == 1 && !datatype.index) + return busValueToUnicode(payload, payload_length, datatype, value); + + // DPT 29.* - Signed 64 Bit Integer + if (datatype.mainGroup == 29 && datatype.subGroup >= 10 && datatype.subGroup <= 12 && !datatype.index) + return busValueToSigned64(payload, payload_length, datatype, value); + + // DPT 219.* - Alarm Info + if (datatype.mainGroup == 219 && datatype.subGroup == 1 && datatype.index <= 10) + return busValueToAlarmInfo(payload, payload_length, datatype, value); + + // DPT 221.* - Serial Number + if (datatype.mainGroup == 221 && datatype.subGroup == 1 && datatype.index <= 1) + return busValueToSerialNumber(payload, payload_length, datatype, value); + + // DPT 217.* - Version + if (datatype.mainGroup == 217 && datatype.subGroup == 1 && datatype.index <= 2) + return busValueToVersion(payload, payload_length, datatype, value); + + // DPT 225.001/225.002 - Scaling Speed and Scaling Step Time + if (datatype.mainGroup == 225 && datatype.subGroup >= 1 && datatype.subGroup <= 2 && datatype.index <= 1) + return busValueToScaling(payload, payload_length, datatype, value); + + // DPT 225.003 - Next Tariff + if (datatype.mainGroup == 225 && datatype.subGroup == 3 && datatype.index <= 1) + return busValueToTariff(payload, payload_length, datatype, value); + + // DPT 231.* - Locale + if (datatype.mainGroup == 231 && datatype.subGroup == 1 && datatype.index <= 1) + return busValueToLocale(payload, payload_length, datatype, value); + + // DPT 232.600 - RGB + if (datatype.mainGroup == 232 && datatype.subGroup == 600 && !datatype.index) + return busValueToRGB(payload, payload_length, datatype, value); + + // DPT 234.* - Language and Region + if (datatype.mainGroup == 234 && datatype.subGroup >= 1 && datatype.subGroup <= 2 && !datatype.index) + return busValueToLocale(payload, payload_length, datatype, value); + + // DPT 235.* - Active Energy + if (datatype.mainGroup == 235 && datatype.subGroup == 1 && datatype.index <= 3) + return busValueToActiveEnergy(payload, payload_length, datatype, value); + + // DPT 238.* - Scene Config + if (datatype.mainGroup == 238 && datatype.subGroup == 1 && datatype.index <= 2) + return busValueToSceneConfig(payload, payload_length, datatype, value); + + // DPT 239.* - Flagged Scaling + if (datatype.mainGroup == 239 && datatype.subGroup == 1 && datatype.index <= 1) + return busValueToFlaggedScaling(payload, payload_length, datatype, value); + + // DPT 251.600 - RGBW + if (datatype.mainGroup == 251 && datatype.subGroup == 600 && datatype.index <= 1) + return busValueToRGBW(payload, payload_length, datatype, value); + } + + return false; +} + +bool KNX_Encode_Value(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if (datatype.mainGroup == 1 && datatype.subGroup >= 1 && datatype.subGroup <= 23 && datatype.subGroup != 20 && !datatype.index) + return valueToBusValueBinary(value, payload, payload_length, datatype); + + // DPT 2.* - Binary Control + if (datatype.mainGroup == 2 && datatype.subGroup >= 1 && datatype.subGroup <= 12 && datatype.index <= 1) + return valueToBusValueBinaryControl(value, payload, payload_length, datatype); + + // DPT 3.* - Step Control + if (datatype.mainGroup == 3 && datatype.subGroup >= 7 && datatype.subGroup <= 8 && datatype.index <= 1) + return valueToBusValueStepControl(value, payload, payload_length, datatype); + + // DPT 4.* - Character + if (datatype.mainGroup == 4 && datatype.subGroup >= 1 && datatype.subGroup <= 2 && !datatype.index) + return valueToBusValueCharacter(value, payload, payload_length, datatype); + + // DPT 5.* - Unsigned 8 Bit Integer + if (datatype.mainGroup == 5 && ((datatype.subGroup >= 1 && datatype.subGroup <= 6 && datatype.subGroup != 2) || datatype.subGroup == 10) && !datatype.index) + return valueToBusValueUnsigned8(value, payload, payload_length, datatype); + + // DPT 6.001/6.010 - Signed 8 Bit Integer + if (datatype.mainGroup == 6 && (datatype.subGroup == 1 || datatype.subGroup == 10) && !datatype.index) + return valueToBusValueSigned8(value, payload, payload_length, datatype); + + // DPT 6.020 - Status with Mode + if (datatype.mainGroup == 6 && datatype.subGroup == 20 && datatype.index <= 5) + return valueToBusValueStatusAndMode(value, payload, payload_length, datatype); + + // DPT 7.001/7.010/7.011/7.012/7.013/7.600 - Unsigned 16 Bit Integer + if (datatype.mainGroup == 7 && (datatype.subGroup == 1 || (datatype.subGroup >= 10 && datatype.subGroup <= 13) || datatype.subGroup == 600) && !datatype.index) + return valueToBusValueUnsigned16(value, payload, payload_length, datatype); + + // DPT 7.002-DPT 7.007 - Time Period + if (datatype.mainGroup == 7 && datatype.subGroup >= 2 && datatype.subGroup <= 7 && !datatype.index) + return valueToBusValueTimePeriod(value, payload, payload_length, datatype); + + // DPT 8.001/8.010/8.011 - Signed 16 Bit Integer + if (datatype.mainGroup == 8 && (datatype.subGroup == 1 || datatype.subGroup == 10 || datatype.subGroup == 11) && !datatype.index) + return valueToBusValueSigned16(value, payload, payload_length, datatype); + + // DPT 8.002-DPT 8.007 - Time Delta + if (datatype.mainGroup == 8 && datatype.subGroup >= 2 && datatype.subGroup <= 7 && !datatype.index) + return valueToBusValueTimeDelta(value, payload, payload_length, datatype); + + // DPT 9.* - 16 Bit Float + if (datatype.mainGroup == 9 && ((datatype.subGroup >= 1 && datatype.subGroup <= 11 ) || (datatype.subGroup >= 20 && datatype.subGroup <= 29)) && !datatype.index) + return valueToBusValueFloat16(value, payload, payload_length, datatype); + + // DPT 10.* - Time and Weekday + if (datatype.mainGroup == 10 && datatype.subGroup == 1 && datatype.index <= 1) + return valueToBusValueTime(value, payload, payload_length, datatype); + + // DPT 11.* - Date + if (datatype.mainGroup == 11 && datatype.subGroup == 1 && !datatype.index) + return valueToBusValueDate(value, payload, payload_length, datatype); + + // DPT 12.* - Unsigned 32 Bit Integer + if (datatype.mainGroup == 12 && datatype.subGroup == 1 && !datatype.index) + return valueToBusValueUnsigned32(value, payload, payload_length, datatype); + + // DPT 13.001/13.002/13.010-13.015 - Signed 32 Bit Integer + if (datatype.mainGroup == 13 && (datatype.subGroup == 1 || datatype.subGroup == 2 || (datatype.subGroup >= 10 && datatype.subGroup <= 15)) && !datatype.index) + return valueToBusValueSigned32(value, payload, payload_length, datatype); + + // DPT 13.100 - Long Time Period + if (datatype.mainGroup == 13 && datatype.subGroup == 100 && !datatype.index) + return valueToBusValueLongTimePeriod(value, payload, payload_length, datatype); + + // DPT 14.* - 32 Bit Float + if (datatype.mainGroup == 14 && datatype.subGroup <= 79 && !datatype.index) + return valueToBusValueFloat32(value, payload, payload_length, datatype); + + // DPT 15.* - Access Data + if (datatype.mainGroup == 15 && !datatype.subGroup && datatype.index <= 5) + return valueToBusValueAccess(value, payload, payload_length, datatype); + + // DPT 16.* - String + if (datatype.mainGroup == 16 && datatype.subGroup <= 1 && !datatype.index) + return valueToBusValueString(value, payload, payload_length, datatype); + + // DPT 17.* - Scene Number + if (datatype.mainGroup == 17 && datatype.subGroup == 1 && !datatype.index) + return valueToBusValueScene(value, payload, payload_length, datatype); + + // DPT 18.* - Scene Control + if (datatype.mainGroup == 18 && datatype.subGroup == 1 && datatype.index <= 1) + return valueToBusValueSceneControl(value, payload, payload_length, datatype); + + // DPT 19.* - Date and Time + if (datatype.mainGroup == 19 && datatype.subGroup == 1 && (datatype.index <= 3 || datatype.index == 9 || datatype.index == 10)) + return valueToBusValueDateTime(value, payload, payload_length, datatype); + + // DPT 20.* - HVAC Control mode Unsigned 8 Bit Integer + if (datatype.mainGroup == 20 && !datatype.index) + return valueToBusValueUnsigned8(value, payload, payload_length, datatype); + + // DPT 26.* - Scene Info + if (datatype.mainGroup == 26 && datatype.subGroup == 1 && datatype.index <= 1) + return valueToBusValueSceneInfo(value, payload, payload_length, datatype); + + // DPT 27.001 - 32 Bit Field + if (datatype.mainGroup == 27 && datatype.subGroup == 1 && !datatype.index) + return valueToBusValueUnsigned32(value, payload, payload_length, datatype); + + // DPT 28.* - Unicode String + if (datatype.mainGroup == 28 && datatype.subGroup == 1 && !datatype.index) + return valueToBusValueUnicode(value, payload, payload_length, datatype); + + // DPT 29.* - Signed 64 Bit Integer + if (datatype.mainGroup == 29 && datatype.subGroup >= 10 && datatype.subGroup <= 12 && !datatype.index) + return valueToBusValueSigned64(value, payload, payload_length, datatype); + + // DPT 219.* - Alarm Info + if (datatype.mainGroup == 219 && datatype.subGroup == 1 && datatype.index <= 10) + return valueToBusValueAlarmInfo(value, payload, payload_length, datatype); + + // DPT 221.* - Serial Number + if (datatype.mainGroup == 221 && datatype.subGroup == 1 && datatype.index <= 1) + return valueToBusValueSerialNumber(value, payload, payload_length, datatype); + + // DPT 217.* - Version + if (datatype.mainGroup == 217 && datatype.subGroup == 1 && datatype.index <= 2) + return valueToBusValueVersion(value, payload, payload_length, datatype); + + // DPT 225.001/225.002 - Scaling Speed and Scaling Step Time + if (datatype.mainGroup == 225 && datatype.subGroup >= 1 && datatype.subGroup <= 2 && datatype.index <= 1) + return valueToBusValueScaling(value, payload, payload_length, datatype); + + // DPT 225.003 - Next Tariff + if (datatype.mainGroup == 225 && datatype.subGroup == 3 && datatype.index <= 1) + return valueToBusValueTariff(value, payload, payload_length, datatype); + + // DPT 231.* - Locale + if (datatype.mainGroup == 231 && datatype.subGroup == 1 && datatype.index <= 1) + return valueToBusValueLocale(value, payload, payload_length, datatype); + + // DPT 232.600 - RGB + if (datatype.mainGroup == 232 && datatype.subGroup == 600 && !datatype.index) + return valueToBusValueRGB(value, payload, payload_length, datatype); + + // DPT 234.* - Language and Region + if (datatype.mainGroup == 234 && datatype.subGroup >= 1 && datatype.subGroup <= 2 && !datatype.index) + return valueToBusValueLocale(value, payload, payload_length, datatype); + + // DPT 235.* - Active Energy + if (datatype.mainGroup == 235 && datatype.subGroup == 1 && datatype.index <= 3) + return valueToBusValueActiveEnergy(value, payload, payload_length, datatype); + + // DPT 238.* - Scene Config + if (datatype.mainGroup == 238 && datatype.subGroup == 1 && datatype.index <= 2) + return valueToBusValueSceneConfig(value, payload, payload_length, datatype); + + // DPT 239.* - Flagged Scaling + if (datatype.mainGroup == 239 && datatype.subGroup == 1 && datatype.index <= 1) + return valueToBusValueFlaggedScaling(value, payload, payload_length, datatype); + + // DPT 251.600 - RGBW + if (datatype.mainGroup == 251 && datatype.subGroup == 600 && datatype.index <= 1) + return valueToBusValueRGBW(value, payload, payload_length, datatype); + + return false; +} + +bool busValueToBinary(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + value = bitFromPayload(payload, 7); + return true; +} + +bool busValueToBinaryControl(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + value = (uint8_t)(unsigned8FromPayload(payload, 0) & 0x03); + return true; +} + +bool busValueToStepControl(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + value = (uint8_t)(unsigned8FromPayload(payload, 0) & 0x0F); + return true; +} +bool busValueToCharacter(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + int8_t charValue = signed8FromPayload(payload, 0); + + if (datatype.subGroup == 1 && (charValue & 0x80)) + return false; + + if (datatype.subGroup == 2) + { + value = (uint8_t)charValue; + return true; + } + + value = charValue; + return true; +} + +bool busValueToUnsigned8(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + + switch (datatype.subGroup) + { + case 1: + value = (uint8_t)round(unsigned8FromPayload(payload, 0) * 100.0 / 255.0); + return true; + + case 3: + value = (uint8_t)round(unsigned8FromPayload(payload, 0) * 360.0 / 255.0); + return true; + + case 6: + { + uint8_t numValue = unsigned8FromPayload(payload, 0); + + if (numValue == 0xFF) + return false; + + value = numValue; + return true; + } + } + + value = unsigned8FromPayload(payload, 0); + return true; +} + +bool busValueToSigned8(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + value = (uint8_t)(unsigned8FromPayload(payload, 0)); + return true; +} + +bool busValueToStatusAndMode(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + + if (datatype.index < 5) + { + value = bitFromPayload(payload, datatype.index); + return true; + } + else if (datatype.index == 5) + { + value = (uint8_t)(unsigned8FromPayload(payload, 0) & 0x07); + return true; + } + + return false; +} + +bool busValueToUnsigned16(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(2); + value = unsigned16FromPayload(payload, 0); + return true; +} + +bool busValueToTimePeriod(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(2); + + int64_t duration = unsigned16FromPayload(payload, 0); + value = duration; + return true; +} + +bool busValueToSigned16(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(2); + + if (datatype.subGroup == 10) + { + value = signed16FromPayload(payload, 0) / 100.0; + return true; + } + + value = signed16FromPayload(payload, 0); + return true; +} + +bool busValueToTimeDelta(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(2); + + int64_t duration = signed16FromPayload(payload, 0); + value = duration; + return true; +} + +bool busValueToFloat16(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(2); + + if (unsigned16FromPayload(payload, 0) == 0x7FFF) + return false; + + value = float16FromPayload(payload, 0); + return true; +} + +bool busValueToTime(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(3); + + switch (datatype.index) + { + case 0: + value = (uint8_t)((unsigned8FromPayload(payload, 0) >> 5) & 0x07); + return true; + + case 1: + { + unsigned char hours = unsigned8FromPayload(payload, 0) & 0x1F; + unsigned char weekDay = (unsigned8FromPayload(payload, 0) & 0xE0) >> 5; + unsigned char minutes = unsigned8FromPayload(payload, 1) & 0x3F; + unsigned char seconds = unsigned8FromPayload(payload, 2) & 0x3F; + + if (hours > 23 || minutes > 59 || seconds > 59) + return false; + + struct tm tmp; + memset(&tmp, 0, sizeof(tmp)); + tmp.tm_hour = hours; + tmp.tm_wday = weekDay; + tmp.tm_min = minutes; + tmp.tm_sec = seconds; + value = tmp; + return true; + } + } + + return false; +} + +bool busValueToDate(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(3); + unsigned short year = unsigned8FromPayload(payload, 2) & 0x7F; + unsigned char month = unsigned8FromPayload(payload, 1) & 0x0F; + unsigned char day = unsigned8FromPayload(payload, 0) & 0x1F; + + if (year > 99 || month < 1 || month > 12 || day < 1) + return false; + + struct tm tmp; + memset(&tmp, 0, sizeof(tmp)); + year += year >= 90 ? 1900 : 2000; + tmp.tm_mday = day; + tmp.tm_year = year; + tmp.tm_mon = month; + value = tmp; + return true; +} + +bool busValueToUnsigned32(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(4); + value = unsigned32FromPayload(payload, 0); + return true; +} + +bool busValueToSigned32(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(4); + value = signed32FromPayload(payload, 0); + return true; +} + +bool busValueToLongTimePeriod(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(4); + value = signed32FromPayload(payload, 0); + return true; +} + +bool busValueToFloat32(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(4); + value = float32FromPayload(payload, 0); + return true; +} + +bool busValueToAccess(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(4); + + switch (datatype.index) + { + case 0: + { + int32_t digits = 0; + + for (int n = 0, factor = 100000; n < 6; ++n, factor /= 10) + { + unsigned char digit = bcdFromPayload(payload, n); + + if (digit > 9) + return false; + + digits += digit * factor; + } + + value = digits; + return true; + } + + case 1: + case 2: + case 3: + case 4: + value = bitFromPayload(payload, 23 + datatype.index); + return true; + + case 5: + value = bcdFromPayload(payload, 7); + return true; + } + + return false; +} + +bool busValueToString(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(14); + + for (int n = 0; n < 14; ++n) + { + auto value = signed8FromPayload(payload, n); + + if (!datatype.subGroup && (value & 0x80)) + return false; + } + + value = (const char*) payload; + return true; +} + +bool busValueToScene(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + value = (uint8_t)(unsigned8FromPayload(payload, 0) & 0x3F); + return true; +} + +bool busValueToSceneControl(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + + switch (datatype.index) + { + case 0: + { + value = bitFromPayload(payload, 0); + return true; + } + + case 1: + { + value = (uint8_t)(unsigned8FromPayload(payload, 0) & 0x3F); + return true; + } + } + + return false; +} + +bool busValueToSceneInfo(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + + switch (datatype.index) + { + case 0: + { + value = bitFromPayload(payload, 1); + return true; + } + + case 1: + { + value = (uint8_t)(unsigned8FromPayload(payload, 0) & 0x3F); + return true; + } + } + + return false; +} + +bool busValueToSceneConfig(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(1); + + switch (datatype.index) + { + case 0: + { + value = (uint8_t)(unsigned8FromPayload(payload, 0) & 0x3F); + return true; + } + + case 1: + case 2: + { + value = bitFromPayload(payload, 2 - datatype.index); + return true; + } + } + + return false; +} + +bool busValueToDateTime(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(8); + + if (datatype.index == 3) + { + value = bitFromPayload(payload, 48); + return true; + } + + if (!bitFromPayload(payload, 48)) + { + switch (datatype.index) + { + case 0: + { + if (bitFromPayload(payload, 51) || bitFromPayload(payload, 52)) + return false; + + unsigned short year = unsigned8FromPayload(payload, 0) + 1900; + unsigned short month = unsigned8FromPayload(payload, 1) & 0x0F; + unsigned short day = unsigned8FromPayload(payload, 2) & 0x1F; + unsigned short hours = unsigned8FromPayload(payload, 3) & 0x1F; + unsigned short minutes = unsigned8FromPayload(payload, 4) & 0x3F; + unsigned short seconds = unsigned8FromPayload(payload, 5) & 0x3F; + + if ((month < 1 || month > 12 || day < 1)) + return false; + + if ((hours > 24 || minutes > 59 || seconds > 59)) + return false; + + struct tm tmp; + memset(&tmp, 0, sizeof(tmp)); + tmp.tm_sec = seconds; + tmp.tm_min = minutes; + tmp.tm_hour = hours; + tmp.tm_mday = day; + tmp.tm_mon = month; + tmp.tm_year = year; + value = tmp; + return true; + } + + case 1: + { + if (bitFromPayload(payload, 53)) + return false; + + value = (uint8_t)((unsigned8FromPayload(payload, 3) >> 5) & 0x07); + return true; + } + + case 2: + { + if (bitFromPayload(payload, 50)) + return false; + + value = bitFromPayload(payload, 49); + return true; + } + + case 9: + { + value = bitFromPayload(payload, 55); + return true; + } + + case 10: + { + value = bitFromPayload(payload, 56); + return true; + } + } + } + + return false; +} + +bool busValueToUnicode(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + //TODO + return false; +} + +bool busValueToSigned64(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(8); + value = signed64FromPayload(payload, 0); + return true; +} + +bool busValueToAlarmInfo(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(6); + + switch (datatype.index) + { + case 1: + { + unsigned char prio = unsigned8FromPayload(payload, 1); + + if (prio > 3) + return false; + + value = prio; + return true; + } + + case 0: + case 2: + case 3: + value = unsigned8FromPayload(payload, datatype.index); + return true; + + case 4: + case 5: + case 6: + case 7: + value = bitFromPayload(payload, 43 - datatype.index); + return true; + + case 8: + case 9: + case 10: + value = bitFromPayload(payload, 55 - datatype.index); + return true; + } + + return false; +} + +bool busValueToSerialNumber(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(6); + + switch (datatype.index) + { + case 0: + value = unsigned16FromPayload(payload, 0); + return true; + + case 1: + value = unsigned32FromPayload(payload, 2); + return true; + } + + return false; +} + +bool busValueToVersion(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(2); + + switch (datatype.index) + { + case 0: + value = (uint8_t)((unsigned8FromPayload(payload, 0) >> 3) & 0x1F); + return true; + + case 1: + value = (uint16_t)((unsigned16FromPayload(payload, 0) >> 6) & 0x1F); + return true; + + case 2: + value = (uint8_t)(unsigned8FromPayload(payload, 1) & 0x3F); + return true; + } + + return false; +} + +bool busValueToScaling(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(3); + + switch (datatype.index) + { + case 0: + value = unsigned16FromPayload(payload, 0); + return true; + + case 1: + value = (uint8_t)(unsigned8FromPayload(payload, 2) * 100.0 / 255.0); + return true; + } + + return false; +} + +bool busValueToTariff(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(3); + + switch (datatype.index) + { + case 0: + value = unsigned16FromPayload(payload, 0); + return true; + + case 1: + { + uint8_t tariff = unsigned8FromPayload(payload, 2); + + if (tariff > 254) + return false; + + value = tariff; + return true; + } + } + + return false; +} + +bool busValueToLocale(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(datatype.mainGroup == 231 ? 4 : 2); + + if (!datatype.index || (datatype.mainGroup == 231 && datatype.index == 1)) + { + char code[2]; + code[0] = unsigned8FromPayload(payload, datatype.index * 2); + code[1] = unsigned8FromPayload(payload, datatype.index * 2 + 1); + value = code; + return true; + } + + return false; +} + +bool busValueToRGB(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(3); + uint32_t rgb = (unsigned16FromPayload(payload, 0) << 8) | unsigned8FromPayload(payload, 2); + value = rgb; + return true; +} + +bool busValueToRGBW(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(6); + + switch (datatype.index) + { + case 0: // The RGBW value + { + uint32_t rgbw = unsigned32FromPayload(payload, 0); + value = rgbw; + return true; + } + case 1: // The mask bits only + { + value = unsigned8FromPayload(payload, 5); + return true; + } + } + + return false; +} + +bool busValueToFlaggedScaling(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(2); + + switch (datatype.index) + { + case 0: + value = (uint8_t)(unsigned8FromPayload(payload, 0) * 100.0 / 255.0); + return true; + + case 1: + value = bitFromPayload(payload, 15); + return true; + } + + return false; +} + +bool busValueToActiveEnergy(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) +{ + ASSERT_PAYLOAD(6); + + switch (datatype.index) + { + case 0: + value = signed32FromPayload(payload, 0); + return true; + + case 1: + value = unsigned8FromPayload(payload, 4); + return true; + + case 2: + case 3: + value = bitFromPayload(payload, datatype.index + 44); + return true; + } + + return false; +} + +//------------------------------------------------------------------------------------------------------------------------------------- + +bool valueToBusValueBinary(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + bitToPayload(payload, payload_length, 7, value); + return true; +} + +bool valueToBusValueBinaryControl(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value, 0x03); + return true; +} + +bool valueToBusValueStepControl(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value, 0x0F); + return true; +} + +bool valueToBusValueCharacter(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((uint64_t)value > INT64_C(255) || (datatype.subGroup == 1 && (uint64_t)value > INT64_C(127))) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFF); + return true; +} + +bool valueToBusValueUnsigned8(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(0)) + return false; + + switch (datatype.subGroup) + { + case 1: + { + if ((double)value > 100.0) + return false; + + unsigned8ToPayload(payload, payload_length, 0, round((double)value * 255.0 / 100.0), 0xFF); + break; + } + + case 3: + { + if ((double)value > 360.0) + return false; + + unsigned8ToPayload(payload, payload_length, 0, round((double)value * 255.0 / 360.0), 0xFF); + break; + } + + case 6: + { + if ((int64_t)value > INT64_C(254)) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFF); + break; + } + + default: + { + if ((int64_t)value > INT64_C(255)) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFF); + } + } + + return true; +} + +bool valueToBusValueSigned8(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(-128) || (int64_t)value > INT64_C(127)) + return false; + + signed8ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFF); + return true; +} + +bool valueToBusValueStatusAndMode(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if (datatype.index < 5) + bitToPayload(payload, payload_length, datatype.index, value); + else if (datatype.index == 5) + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(7)) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value, 0x07); + } + else + return false; + + return true; +} + +bool valueToBusValueUnsigned16(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(65535)) + return false; + + unsigned16ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFFFF); + return true; +} + +bool valueToBusValueTimePeriod(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + struct tm tmp = value; + time_t timeSinceEpoch = mktime(&tmp); + + if (timeSinceEpoch < INT64_C(0) || timeSinceEpoch > INT64_C(65535)) + return false; + + unsigned16ToPayload(payload, payload_length, 0, timeSinceEpoch, 0xFFFF); + return true; +} + +bool valueToBusValueSigned16(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(-32768) || (int64_t)value > INT64_C(32767)) + return false; + + if (datatype.subGroup == 10) + { + if ((double)value < -327.68 || (double)value > 327.67) + return false; + + signed16ToPayload(payload, payload_length, 0, (int16_t)((double)value * 100.0), 0xFFFF); + } + else + signed16ToPayload(payload, payload_length, 0, (uint64_t)value, 0xffff); + + return true; +} + +bool valueToBusValueTimeDelta(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + struct tm tmp = value; + time_t timeSinceEpoch = mktime(&tmp); + + if (timeSinceEpoch < INT64_C(-32768) || timeSinceEpoch > INT64_C(32767)) + return false; + + signed16ToPayload(payload, payload_length, 0, timeSinceEpoch, 0xFFFF); + return true; +} + +bool valueToBusValueFloat16(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + double numValue = value; + + // bigger values like 670760.0 result in 0x7FFF which denotes invalid data. + // I'm not sure if the GO shouldn't be updated to this value instead + if (numValue > 670433.28) + return false; + + if (numValue < -671088.64) + return false; + + switch (datatype.subGroup) + { + case 1: + if (numValue < -273.0) + return false; + + break; + + case 2: + case 3: + case 10: + case 11: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + if (numValue < -670760.0) + return false; + + break; + + case 4: + case 5: + case 6: + case 7: + case 8: + case 28: + if (numValue < 0.0) + return false; + + break; + + case 27: + if (numValue < -459.6) + return false; + + break; + } + + float16ToPayload(payload, payload_length, 0, numValue, 0xFFFF); + return true; +} + +bool valueToBusValueTime(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(7)) + return false; + + ENSURE_PAYLOAD(3); + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value << 5, 0xE0); + break; + } + + case 1: + { + struct tm tmp = value; + unsigned8ToPayload(payload, payload_length, 0, tmp.tm_hour, 0x1F); + unsigned8ToPayload(payload, payload_length, 1, tmp.tm_min, 0x3F); + unsigned8ToPayload(payload, payload_length, 2, tmp.tm_sec, 0x3F); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueDate(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + struct tm tmp = value; + + if (tmp.tm_year < 1990 || tmp.tm_year > 2089) + return false; + + unsigned8ToPayload(payload, payload_length, 0, tmp.tm_mday, 0x1F); + unsigned8ToPayload(payload, payload_length, 1, tmp.tm_mon, 0x0F); + unsigned8ToPayload(payload, payload_length, 2, tmp.tm_year % 100, 0x7F); + return true; +} + +bool valueToBusValueUnsigned32(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(4294967295)) + return false; + + unsigned32ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFFFFFFFF); + return true; +} + +bool valueToBusValueSigned32(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(-2147483648) || (int64_t)value > INT64_C(2147483647)) + return false; + + signed32ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFFFFFFFF); + return true; +} + +bool valueToBusValueLongTimePeriod(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(-2147483648) || (int64_t)value > INT64_C(2147483647)) + return false; + + signed32ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFFFFFFFF); + return true; +} + +bool valueToBusValueFloat32(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + double numValue = value; + + if (numValue < (-8388608.0 * pow(2, 255)) || numValue > (8388607.0 * pow(2, 255))) + return false; + + float32ToPayload(payload, payload_length, 0, numValue, 0xFFFFFFFF); + return true; +} + +bool valueToBusValueAccess(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(999999)) + return false; + + ENSURE_PAYLOAD(4); + + for (int n = 0, factor = 100000; n < 6; ++n, factor /= 10) + bcdToPayload(payload, payload_length, n, ((uint64_t)value / factor) % 10); + + break; + } + + case 1: + case 2: + case 3: + case 4: + bitToPayload(payload, payload_length, 23 + datatype.index, value); + break; + + case 5: + { + if ((uint64_t)value > INT64_C(15)) + return false; + + bcdToPayload(payload, payload_length, 7, (uint64_t)value); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueString(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + const char* strValue = value; + uint8_t val = strValue[0]; + + for (int n = 0; n < 14; n++) + { + if (val) + val = strValue[n]; //string terminator 0x00 will stop further assignments and init the remainig payload with zero + + unsigned8ToPayload(payload, payload_length, n, val, 0xff); + } + + return true; +} + +bool valueToBusValueScene(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(63)) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (uint64_t)value, 0xFF); + return true; +} + +bool valueToBusValueSceneControl(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + bitToPayload(payload, payload_length, 0, value); + break; + + case 1: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(63)) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (int64_t)value, 0x3F); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueSceneInfo(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + bitToPayload(payload, payload_length, 1, value); + break; + + case 1: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(63)) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (int64_t)value, 0x3F); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueSceneConfig(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(63)) + return false; + + unsigned8ToPayload(payload, payload_length, 0, (int64_t)value, 0x3F); + break; + } + + case 1: + case 2: + bitToPayload(payload, payload_length, 2 - datatype.index, value); + break; + + default: + return false; + } + + return true; +} + +bool valueToBusValueDateTime(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + struct tm local = value; + time_t time = mktime(&local); + + if (!time) //TODO add check if date or time is invalid + return false; + + ENSURE_PAYLOAD(8); + struct tm tmp = value; + bitToPayload(payload, payload_length, 51, false); + bitToPayload(payload, payload_length, 52, false); + unsigned8ToPayload(payload, payload_length, 0, tmp.tm_year - 1900, 0xFF); + unsigned8ToPayload(payload, payload_length, 1, tmp.tm_mon, 0x0F); + unsigned8ToPayload(payload, payload_length, 2, tmp.tm_mday, 0x1F); + + bitToPayload(payload, payload_length, 54, false); + unsigned8ToPayload(payload, payload_length, 3, tmp.tm_hour, 0x1F); + unsigned8ToPayload(payload, payload_length, 4, tmp.tm_min, 0x3F); + unsigned8ToPayload(payload, payload_length, 5, tmp.tm_sec, 0x3F); + break; + } + + case 1: + { + ENSURE_PAYLOAD(8); + + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(7)) + bitToPayload(payload, payload_length, 53, true); + else + { + bitToPayload(payload, payload_length, 53, false); + unsigned8ToPayload(payload, payload_length, 3, (int64_t)value << 5, 0xE0); + } + + break; + } + + case 2: + { + ENSURE_PAYLOAD(8); + bitToPayload(payload, payload_length, 49, value); + bitToPayload(payload, payload_length, 50, false); + break; + } + + case 3: + { + ENSURE_PAYLOAD(8); + bitToPayload(payload, payload_length, 48, value); + break; + } + + case 9: + { + ENSURE_PAYLOAD(8); + bitToPayload(payload, payload_length, 55, value); + break; + } + + case 10: + { + bitToPayload(payload, payload_length, 56, value); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueUnicode(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + //TODO + return false; +} + +bool valueToBusValueSigned64(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + signed64ToPayload(payload, payload_length, 0, (int64_t)value, UINT64_C(0xFFFFFFFFFFFFFFFF)); + return true; +} + +bool valueToBusValueAlarmInfo(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 1: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(3)) + return false; + + ENSURE_PAYLOAD(6); + unsigned8ToPayload(payload, payload_length, 1, (int64_t)value, 0xFF); + break; + } + + case 0: + case 2: + case 3: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(255)) + return false; + + ENSURE_PAYLOAD(6); + unsigned8ToPayload(payload, payload_length, datatype.index, (int64_t)value, 0xFF); + break; + } + + case 4: + case 5: + case 6: + case 7: + { + ENSURE_PAYLOAD(6); + bitToPayload(payload, payload_length, 43 - datatype.index, value); + break; + } + + case 8: + case 9: + case 10: + { + ENSURE_PAYLOAD(6); + bitToPayload(payload, payload_length, 55 - datatype.index, value); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueSerialNumber(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(65535)) + return false; + + ENSURE_PAYLOAD(6); + unsigned16ToPayload(payload, payload_length, 0, (int64_t)value, 0xFFFF); + break; + } + + case 1: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(4294967295)) + return false; + + ENSURE_PAYLOAD(6); + unsigned32ToPayload(payload, payload_length, 2, (int64_t)value, 0xFFFF); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueVersion(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(31)) + return false; + + ENSURE_PAYLOAD(2); + unsigned8ToPayload(payload, payload_length, 0, (int64_t)value << 3, 0xF8); + break; + } + + case 1: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(31)) + return false; + + unsigned16ToPayload(payload, payload_length, 0, (int64_t)value << 6, 0x07C0); + break; + } + + case 2: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(63)) + return false; + + unsigned8ToPayload(payload, payload_length, 1, (int64_t)value, 0x3F); + break; + } + + default: + return false; + } + + return true; +} + +bool valueToBusValueScaling(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + uint32_t duration = value; + + if (duration > INT64_C(65535)) + return false; + + ENSURE_PAYLOAD(3); + unsigned16ToPayload(payload, payload_length, 0, duration, 0xFFFF); + return true; + } + + case 1: + { + if ((double)value < 0.0 || (double)value > 100.0) + return false; + + unsigned8ToPayload(payload, payload_length, 2, round((double)value * 255.0 / 100.0), 0xff); + break; + } + } + + return true; +} + +bool valueToBusValueTariff(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + uint32_t duration = value; + + if (duration > INT64_C(65535)) + return false; + + ENSURE_PAYLOAD(3); + unsigned16ToPayload(payload, payload_length, 0, duration, 0xFFFF); + return true; + } + + case 1: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(254)) + return false; + + unsigned8ToPayload(payload, payload_length, 2, (int64_t)value, 0xff); + break; + } + } + + return true; +} + +bool valueToBusValueLocale(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + int strl = strlen(value); + + if (strl != 2) + return false; + + if (!datatype.index || (datatype.mainGroup == 231 && datatype.index == 1)) + { + ENSURE_PAYLOAD(datatype.mainGroup == 231 ? 4 : 2); + unsigned8ToPayload(payload, payload_length, datatype.index * 2, ((const char*)value)[0], 0xff); + unsigned8ToPayload(payload, payload_length, datatype.index * 2 + 1, ((const char*)value)[1], 0xff); + return true; + } + + return false; +} + +bool valueToBusValueRGB(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(0xffffff)) + return false; + + unsigned int rgb = (int64_t)value; + + unsigned16ToPayload(payload, payload_length, 0, rgb >> 8, 0xffff); + unsigned8ToPayload(payload, payload_length, 2, rgb & 0xff, 0xff); + return true; +} + +bool valueToBusValueRGBW(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: // RGBW + { + uint32_t rgbw = (uint32_t)value; + unsigned32ToPayload(payload, payload_length, 0, rgbw, 0xffffffff); // RGBW + break; + } + case 1: // Mask bits + { + unsigned8ToPayload(payload, payload_length, 5, (uint8_t)value, 0x0f); + break; + } + } + + return true; +} + +bool valueToBusValueFlaggedScaling(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + if ((double)value < 0.0 || (double)value > 100.0) + return false; + + ENSURE_PAYLOAD(2); + unsigned8ToPayload(payload, payload_length, 0, round((double)value * 255.0 / 100.0), 0xff); + break; + } + + case 1: + bitToPayload(payload, payload_length, 15, value); + break; + + default: + return false; + } + + return true; +} + +bool valueToBusValueActiveEnergy(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype) +{ + switch (datatype.index) + { + case 0: + { + if ((int64_t)value < INT64_C(-2147483648) || (int64_t)value > INT64_C(2147483647)) + return false; + + ENSURE_PAYLOAD(6); + signed32ToPayload(payload, payload_length, 0, (int64_t)value, 0xffffffff); + break; + } + + case 1: + { + if ((int64_t)value < INT64_C(0) || (int64_t)value > INT64_C(254)) + return false; + + ENSURE_PAYLOAD(6); + unsigned8ToPayload(payload, payload_length, 4, (int64_t)value, 0xff); + break; + } + + case 2: + case 3: + bitToPayload(payload, payload_length, datatype.index + 44, value); + break; + + default: + return false; + } + + return true; +} + +// Helper functions +bool bitFromPayload(const uint8_t* payload, int index) +{ + int bit = payload[index / 8] & (1 << (7 - (index % 8))); + return bit ? true : false; +} +uint8_t unsigned8FromPayload(const uint8_t* payload, int index) +{ + return (uint8_t)payload[index]; +} +int8_t signed8FromPayload(const uint8_t* payload, int index) +{ + return (int8_t)payload[index]; +} +uint16_t unsigned16FromPayload(const uint8_t* payload, int index) +{ + return ((((uint16_t)payload[index]) << 8) & 0xFF00) | (((uint16_t)payload[index + 1]) & 0x00FF); +} +int16_t signed16FromPayload(const uint8_t* payload, int index) +{ + return ((((uint16_t)payload[index]) << 8) & 0xFF00) | (((uint16_t)payload[index + 1]) & 0x00FF); +} +uint32_t unsigned32FromPayload(const uint8_t* payload, int index) +{ + return ((((uint32_t)payload[index]) << 24) & 0xFF000000) | + ((((uint32_t)payload[index + 1]) << 16) & 0x00FF0000) | + ((((uint32_t)payload[index + 2]) << 8) & 0x0000FF00) | + (((uint32_t)payload[index + 3]) & 0x000000FF); +} +int32_t signed32FromPayload(const uint8_t* payload, int index) +{ + return (int32_t)unsigned32FromPayload(payload, index); +} +uint64_t unsigned64FromPayload(const uint8_t* payload, int index) +{ + return ((((uint64_t)payload[index]) << 56) & 0xFF00000000000000) | + ((((uint64_t)payload[index + 1]) << 48) & 0x00FF000000000000) | + ((((uint64_t)payload[index + 2]) << 40) & 0x0000FF0000000000) | + ((((uint64_t)payload[index + 3]) << 32) & 0x000000FF00000000) | + ((((uint64_t)payload[index + 4]) << 24) & 0x00000000FF000000) | + ((((uint64_t)payload[index + 5]) << 16) & 0x0000000000FF0000) | + ((((uint64_t)payload[index + 6]) << 8) & 0x000000000000FF00) | + (((uint64_t)payload[index + 7]) & 0x00000000000000FF); +} +double float16FromPayload(const uint8_t* payload, int index) +{ + uint16_t mantissa = unsigned16FromPayload(payload, index) & 0x87FF; + + if (mantissa & 0x8000) + return ((~mantissa & 0x07FF) + 1.0) * -0.01 * (1 << ((payload[index] >> 3) & 0x0F)); + + return mantissa * 0.01 * (1 << ((payload[index] >> 3) & 0x0F)); +} +float float32FromPayload(const uint8_t* payload, int index) +{ + union + { + float f; + uint32_t i; + } area; + area.i = unsigned32FromPayload(payload, index); + return area.f; +} +double float64FromPayload(const uint8_t* payload, int index) +{ + union + { + double f; + uint64_t i; + } area; + area.i = unsigned64FromPayload(payload, index); + return area.f; +} +int64_t signed64FromPayload(const uint8_t* payload, int index) +{ + return ((((uint64_t)payload[index]) << 56) & UINT64_C(0xFF00000000000000)) | + ((((uint64_t)payload[index + 1]) << 48) & UINT64_C(0x00FF000000000000)) | + ((((uint64_t)payload[index + 2]) << 40) & UINT64_C(0x0000FF0000000000)) | + ((((uint64_t)payload[index + 3]) << 32) & UINT64_C(0x000000FF00000000)) | + ((((uint64_t)payload[index + 4]) << 24) & UINT64_C(0x00000000FF000000)) | + ((((uint64_t)payload[index + 5]) << 16) & UINT64_C(0x0000000000FF0000)) | + ((((uint64_t)payload[index + 6]) << 8) & UINT64_C(0x000000000000FF00)) | + (((uint64_t)payload[index + 7]) & UINT64_C(0x00000000000000FF)); +} +uint8_t bcdFromPayload(const uint8_t* payload, int index) +{ + if (index % 2) + return (uint8_t)(payload[index / 2] & 0x0F); + + return (uint8_t)((payload[index / 2] >> 4) & 0x0F); +} + +void bitToPayload(uint8_t* payload, size_t payload_length, int index, bool value) +{ + ENSURE_PAYLOAD(index / 8 + 1); + payload[index / 8] = (payload[index / 8] & ~(1 << (7 - (index % 8)))) | (value ? (1 << (7 - (index % 8))) : 0); +} +void unsigned8ToPayload(uint8_t* payload, size_t payload_length, int index, uint8_t value, uint8_t mask) +{ + ENSURE_PAYLOAD(index + 1); + payload[index] = (payload[index] & ~mask) | (value & mask); +} +void signed8ToPayload(uint8_t* payload, size_t payload_length, int index, int8_t value, uint8_t mask) +{ + ENSURE_PAYLOAD(index + 1); + payload[index] = (payload[index] & ~mask) | (value & mask); +} +void unsigned16ToPayload(uint8_t* payload, size_t payload_length, int index, uint16_t value, uint16_t mask) +{ + ENSURE_PAYLOAD(index + 2); + payload[index] = (payload[index] & (~mask >> 8)) | ((value >> 8) & (mask >> 8)); + payload[index + 1] = (payload[index + 1] & ~mask) | (value & mask); +} +void signed16ToPayload(uint8_t* payload, size_t payload_length, int index, int16_t value, uint16_t mask) +{ + ENSURE_PAYLOAD(index + 2); + payload[index] = (payload[index] & (~mask >> 8)) | ((value >> 8) & (mask >> 8)); + payload[index + 1] = (payload[index + 1] & ~mask) | (value & mask); +} +void unsigned32ToPayload(uint8_t* payload, size_t payload_length, int index, uint32_t value, uint32_t mask) +{ + ENSURE_PAYLOAD(index + 4); + payload[index] = (payload[index] & (~mask >> 24)) | ((value >> 24) & (mask >> 24)); + payload[index + 1] = (payload[index + 1] & (~mask >> 16)) | ((value >> 16) & (mask >> 16)); + payload[index + 2] = (payload[index + 2] & (~mask >> 8)) | ((value >> 8) & (mask >> 8)); + payload[index + 3] = (payload[index + 3] & ~mask) | (value & mask); +} +void signed32ToPayload(uint8_t* payload, size_t payload_length, int index, int32_t value, uint32_t mask) +{ + ENSURE_PAYLOAD(index + 4); + payload[index] = (payload[index] & (~mask >> 24)) | ((value >> 24) & (mask >> 24)); + payload[index + 1] = (payload[index + 1] & (~mask >> 16)) | ((value >> 16) & (mask >> 16)); + payload[index + 2] = (payload[index + 2] & (~mask >> 8)) | ((value >> 8) & (mask >> 8)); + payload[index + 3] = (payload[index + 3] & ~mask) | (value & mask); +} + +void float16ToPayload(uint8_t* payload, size_t payload_length, int index, double value, uint16_t mask) +{ + bool wasNegative = false; + + if (value < 0) + { + wasNegative = true; + value *= -1; + } + + value *= 100.0; + unsigned short exponent = 0; + + if (value > 2048) + exponent = ceil(log2(value) - 11.0); + + short mantissa = roundf(value / (1 << exponent)); + + // above calculation causes mantissa overflow for values of the form 2^n, where n>11 + if (mantissa >= 0x800) + { + exponent++; + mantissa = roundf(value / (1 << exponent)); + } + + if (wasNegative) + mantissa *= -1; + + // println(mantissa); + + signed16ToPayload(payload, payload_length, index, mantissa, mask); + unsigned8ToPayload(payload, payload_length, index, exponent << 3, 0x78 & (mask >> 8)); +} +void float32ToPayload(uint8_t* payload, size_t payload_length, int index, double value, uint32_t mask) +{ + union + { + float f; + uint32_t i; + } num; + num.f = value; + unsigned32ToPayload(payload, payload_length, index, num.i, mask); +} +void signed64ToPayload(uint8_t* payload, size_t payload_length, int index, int64_t value, uint64_t mask) +{ + ENSURE_PAYLOAD(index + 8); + payload[index] = (payload[index] & (~mask >> 56)) | ((value >> 56) & (mask >> 56)); + payload[index + 1] = (payload[index + 1] & (~mask >> 48)) | ((value >> 48) & (mask >> 48)); + payload[index + 2] = (payload[index + 2] & (~mask >> 40)) | ((value >> 40) & (mask >> 40)); + payload[index + 3] = (payload[index + 3] & (~mask >> 32)) | ((value >> 32) & (mask >> 32)); + payload[index + 4] = (payload[index + 4] & (~mask >> 24)) | ((value >> 24) & (mask >> 24)); + payload[index + 5] = (payload[index + 5] & (~mask >> 16)) | ((value >> 16) & (mask >> 16)); + payload[index + 6] = (payload[index + 6] & (~mask >> 8)) | ((value >> 8) & (mask >> 8)); + payload[index + 7] = (payload[index + 7] & ~mask) | (value & mask); +} +void bcdToPayload(uint8_t* payload, size_t payload_length, int index, uint8_t value) +{ + ENSURE_PAYLOAD(index / 2 + 1); + + if (index % 2) + payload[index / 2] = (payload[index / 2] & 0xF0) | (value & 0x0F); + else + payload[index / 2] = (payload[index / 2] & 0x0F) | ((value << 4) & 0xF0); +} diff --git a/components/knx/src/knx/dptconvert.h b/components/knx/src/knx/dptconvert.h new file mode 100644 index 0000000..b272ed3 --- /dev/null +++ b/components/knx/src/knx/dptconvert.h @@ -0,0 +1,149 @@ +/* + KNX client library - internals + Copyright (C) 2005-2011 Martin Koegler + Copyright (C) 2014 Patrik Pfaffenbauer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + In addition to the permissions in the GNU General Public License, + you may link the compiled version of this file into combinations + with other programs, and distribute those combinations without any + restriction coming from the use of this file. (The General Public + License restrictions do apply in other respects; for example, they + cover modification of the file, and distribution when not linked into + a combine executable.) + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "dpt.h" +#include "knx_value.h" + +/** + * Converts the KNX Payload given by the specific DPT and puts the value in the KNXValue struc + */ +bool KNX_Decode_Value(uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); + +/** + * Converts the KNXValue struct to the KNX Payload as the specific DPT + */ +bool KNX_Encode_Value(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); + +//KNX to internal +bool busValueToBinary(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToBinaryControl(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToStepControl(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToCharacter(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToUnsigned8(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSigned8(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToStatusAndMode(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToUnsigned16(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToTimePeriod(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSigned16(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToTimeDelta(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToFloat16(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToTime(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToDate(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToUnsigned32(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSigned32(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToLongTimePeriod(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToFloat32(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToAccess(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToString(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToScene(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSceneControl(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSceneInfo(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSceneConfig(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToDateTime(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToUnicode(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSigned64(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToAlarmInfo(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToSerialNumber(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToVersion(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToScaling(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToTariff(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToLocale(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToRGB(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToRGBW(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToFlaggedScaling(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); +bool busValueToActiveEnergy(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value); + +//Internal to KNX +bool valueToBusValueBinary(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueBinaryControl(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueStepControl(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueCharacter(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueUnsigned8(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSigned8(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueStatusAndMode(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueUnsigned16(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueTimePeriod(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSigned16(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueTimeDelta(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueFloat16(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueTime(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueDate(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueUnsigned32(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSigned32(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueLongTimePeriod(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueFloat32(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueAccess(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueString(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueScene(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSceneControl(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSceneInfo(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSceneConfig(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueDateTime(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueUnicode(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSigned64(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueAlarmInfo(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueSerialNumber(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueVersion(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueScaling(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueTariff(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueLocale(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueRGB(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueRGBW(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueFlaggedScaling(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); +bool valueToBusValueActiveEnergy(const KNXValue& value, uint8_t* payload, size_t payload_length, const Dpt& datatype); + +//Payload manipulation +bool bitFromPayload(const uint8_t* payload, int index); +uint8_t unsigned8FromPayload(const uint8_t* payload, int index); +int8_t signed8FromPayload(const uint8_t* payload, int index); +uint16_t unsigned16FromPayload(const uint8_t* payload, int index); +int16_t signed16FromPayload(const uint8_t* payload, int index); +uint32_t unsigned32FromPayload(const uint8_t* payload, int index); +int32_t signed32FromPayload(const uint8_t* payload, int index); +uint64_t unsigned64FromPayload(const uint8_t* payload, int index); +double float16FromPayload(const uint8_t* payload, int index); +float float32FromPayload(const uint8_t* payload, int index); +double float64FromPayload(const uint8_t* payload, int index); +int64_t signed64FromPayload(const uint8_t* payload, int index); +uint8_t bcdFromPayload(const uint8_t* payload, int index); + +void bitToPayload(uint8_t* payload, size_t payload_length, int index, bool value); +void unsigned8ToPayload(uint8_t* payload, size_t payload_length, int index, uint8_t value, uint8_t mask); //mask 0xFF +void signed8ToPayload(uint8_t* payload, size_t payload_length, int index, int8_t value, uint8_t mask); //mask 0xFF +void unsigned16ToPayload(uint8_t* payload, size_t payload_length, int index, uint16_t value, uint16_t mask); //mask 0xFFFF +void signed16ToPayload(uint8_t* payload, size_t payload_length, int index, int16_t value, uint16_t mask); //mask 0xFFFF +void unsigned32ToPayload(uint8_t* payload, size_t payload_length, int index, uint32_t value, uint32_t mask); //mask = 0xFFFFFFFF +void signed32ToPayload(uint8_t* payload, size_t payload_length, int index, int32_t value, uint32_t mask); //mask = 0xFFFFFFFF +void float16ToPayload(uint8_t* payload, size_t payload_length, int index, double value, uint16_t mask); //mask = 0xFFFF +void float32ToPayload(uint8_t* payload, size_t payload_length, int index, double value, uint32_t mask); //mask = 0xFFFFFFFF +void signed64ToPayload(uint8_t* payload, size_t payload_length, int index, int64_t value, uint64_t mask); //mask = UINT64_C(0xFFFFFFFFFFFFFFFF) +void bcdToPayload(uint8_t* payload, size_t payload_length, int index, uint8_t value); diff --git a/components/knx/src/knx/function_property.h b/components/knx/src/knx/function_property.h new file mode 100644 index 0000000..ceb6cf2 --- /dev/null +++ b/components/knx/src/knx/function_property.h @@ -0,0 +1,53 @@ +#pragma once + +#include "property.h" + +class InterfaceObject; + +template class FunctionProperty : public Property +{ + public: + FunctionProperty(T* io, PropertyID id, + void (*commandCallback)(T*, uint8_t*, uint8_t, uint8_t*, uint8_t&), + void (*stateCallback)(T*, uint8_t*, uint8_t, uint8_t*, uint8_t&)) + : Property(id, false, PDT_FUNCTION, 1, ReadLv0 | WriteLv0), _interfaceObject(io), _commandCallback(commandCallback), _stateCallback(stateCallback) + /* max_elements is set to 1, read and write level any value so we use Lv0, see 3.3.7 Application Layer p.68 */ + {} + + uint8_t read(uint16_t start, uint8_t count, uint8_t* data) const override + { + return 0; + } + + uint8_t write(uint16_t start, uint8_t count, const uint8_t* data) override + { + return 0; + } + + void command(uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) override + { + if (length == 0 || _commandCallback == nullptr ) + { + resultLength = 0; + return; + } + + _commandCallback(_interfaceObject, data, length, resultData, resultLength); + } + + void state(uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) override + { + if (length == 0 || _stateCallback == nullptr ) + { + resultLength = 0; + return; + } + + _stateCallback(_interfaceObject, data, length, resultData, resultLength); + } + + private: + T* _interfaceObject = nullptr; + void (*_commandCallback)(T*, uint8_t*, uint8_t, uint8_t*, uint8_t&) = nullptr; + void (*_stateCallback)(T*, uint8_t*, uint8_t, uint8_t*, uint8_t&) = nullptr; +}; diff --git a/components/knx/src/knx/group_object.cpp b/components/knx/src/knx/group_object.cpp new file mode 100644 index 0000000..61b51c3 --- /dev/null +++ b/components/knx/src/knx/group_object.cpp @@ -0,0 +1,356 @@ +#include "group_object.h" +#include "bits.h" +#include "string.h" +#include "datapoint_types.h" +#include "group_object_table_object.h" + +#ifdef SMALL_GROUPOBJECT + GroupObjectUpdatedHandler GroupObject::_updateHandlerStatic = 0; +#endif +GroupObjectTableObject* GroupObject::_table = 0; + +GroupObject::GroupObject() +{ + _data = 0; + _uninitialized = true; + _commFlag = Uninitialized; + _dataLength = 0; +#ifndef SMALL_GROUPOBJECT + _updateHandler = 0; +#endif +} + +GroupObject::~GroupObject() +{ + if (_data) + delete[] _data; +} + +bool GroupObject::responseUpdateEnable() +{ + if (!_table) + return false; + + return bitRead(ntohs(_table->_tableData[_asap]), 15) > 0; +} + +bool GroupObject::transmitEnable() +{ + if (!_table) + return false; + + return bitRead(ntohs(_table->_tableData[_asap]), 14) > 0 ; +} + +bool GroupObject::valueReadOnInit() +{ + if (!_table) + return false; + + return bitRead(ntohs(_table->_tableData[_asap]), 13) > 0; +} + +bool GroupObject::writeEnable() +{ + if (!_table) + return false; + + return bitRead(ntohs(_table->_tableData[_asap]), 12) > 0 ; +} + +bool GroupObject::readEnable() +{ + if (!_table) + return false; + + // we forbid reading of new (uninitialized) go + if (_uninitialized) + return false; + + return bitRead(ntohs(_table->_tableData[_asap]), 11) > 0; +} + +bool GroupObject::communicationEnable() +{ + if (!_table) + return false; + + return bitRead(ntohs(_table->_tableData[_asap]), 10) > 0; +} + + +Priority GroupObject::priority() +{ + if (!_table) + return LowPriority; + + return (Priority)((ntohs(_table->_tableData[_asap]) >> 6) & (3 << 2)) ; +} + +uint8_t* GroupObject::valueRef() +{ + return _data; +} + +uint16_t GroupObject::asap() +{ + return _asap; +} + +size_t GroupObject::goSize() +{ + size_t size = sizeInTelegram(); + + if (size == 0) + return 1; + + return size; +} + +// see knxspec 3.5.1 p. 178 +size_t GroupObject::asapValueSize(uint8_t code) const +{ + if (code < 7) + return 0; + + if (code < 8) + return 1; + + if (code < 11 || (code > 20 && code < 255)) + return code - 6; + + switch (code) + { + case 11: + return 6; + + case 12: + return 8; + + case 13: + return 10; + + case 14: + return 14; + + case 15: + return 5; + + case 16: + return 7; + + case 17: + return 9; + + case 18: + return 11; + + case 19: + return 12; + + case 20: + return 13; + + case 255: + return 252; + } + + return -1; +} + + +ComFlag GroupObject::commFlag() +{ + return _commFlag; +} + +void GroupObject::commFlag(ComFlag value) +{ + _commFlag = value; + + if (value == WriteRequest || value == Updated || value == Ok) + _uninitialized = false; +} + +bool GroupObject::initialized() +{ + return !_uninitialized; +} + +void GroupObject::requestObjectRead() +{ + commFlag(ReadRequest); +} + +void GroupObject::objectWritten() +{ + commFlag(WriteRequest); +} + +size_t GroupObject::valueSize() +{ + return _dataLength; +} + +size_t GroupObject::sizeInTelegram() +{ + uint8_t code = lowByte(ntohs(_table->_tableData[_asap])); + return asapValueSize(code); +} + +size_t GroupObject::sizeInMemory() const +{ + uint8_t code = lowByte(ntohs(_table->_tableData[_asap])); + size_t result = asapValueSize(code); + + if (result == 0) + return 1; + + if (code == 14) + return 14 + 1; + + return result; +} + +#ifdef SMALL_GROUPOBJECT +GroupObjectUpdatedHandler GroupObject::classCallback() +{ + return _updateHandlerStatic; +} + +void GroupObject::classCallback(GroupObjectUpdatedHandler handler) +{ + _updateHandlerStatic = handler; +} + +void GroupObject::processClassCallback(GroupObject& ko) +{ + if (_updateHandlerStatic != 0) + _updateHandlerStatic(ko); +} + +#else +void GroupObject::callback(GroupObjectUpdatedHandler handler) +{ + _updateHandler = handler; +} + + +GroupObjectUpdatedHandler GroupObject::callback() +{ + return _updateHandler; +} +#endif + +bool GroupObject::value(const KNXValue& value, const Dpt& type) +{ + if (valueNoSend(value, type)) + { + // write on successful conversion/setting value only + objectWritten(); + return true; + } + return false; +} + + +KNXValue GroupObject::value(const Dpt& type) +{ + KNXValue value = ""; + KNX_Decode_Value(_data, _dataLength, type, value); + return value; +} + +bool GroupObject::tryValue(KNXValue& value, const Dpt& type) +{ + return KNX_Decode_Value(_data, _dataLength, type, value); +} + +#ifndef SMALL_GROUPOBJECT +void GroupObject::dataPointType(Dpt value) +{ + _datapointType = value; +} + + +Dpt GroupObject::dataPointType() +{ + return _datapointType; +} + + +bool GroupObject::tryValue(KNXValue& value) +{ + return tryValue(value, _datapointType); +} + + +bool GroupObject::value(const KNXValue& value) +{ + return this->value(value, _datapointType); +} + + +KNXValue GroupObject::value() +{ + return value(_datapointType); +} + + +bool GroupObject::valueNoSend(const KNXValue& value) +{ + return valueNoSend(value, _datapointType); +} +#endif + +bool GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) +{ + const bool encodingDone = KNX_Encode_Value(value, _data, _dataLength, type); + + // initialize on succesful conversion only + if (encodingDone && _uninitialized) + commFlag(Ok); + + return encodingDone; +} + +bool GroupObject::valueNoSendCompare(const KNXValue& value, const Dpt& type) +{ + if (_uninitialized) + { + // always set first value + return valueNoSend(value, type); + } + else + { + // convert new value to given DPT + uint8_t newData[_dataLength]; + memset(newData, 0, _dataLength); + const bool encodingDone = KNX_Encode_Value(value, newData, _dataLength, type); + if (!encodingDone) + { + // value conversion to DPT failed + // do NOT update the value of the KO! + return false; + } + + // check for change in converted value / update value on change only + const bool dataChanged = memcmp(_data, newData, _dataLength); + + if (dataChanged) + memcpy(_data, newData, _dataLength); + + return dataChanged; + } +} + +bool GroupObject::valueCompare(const KNXValue& value, const Dpt& type) +{ + if (valueNoSendCompare(value, type)) + { + objectWritten(); + return true; + } + + return false; +} \ No newline at end of file diff --git a/components/knx/src/knx/group_object.h b/components/knx/src/knx/group_object.h new file mode 100644 index 0000000..cf65dae --- /dev/null +++ b/components/knx/src/knx/group_object.h @@ -0,0 +1,287 @@ +#pragma once + +#include +#include +#include "knx_types.h" +#include "dptconvert.h" + +class GroupObjectTableObject; + +enum ComFlag : uint8_t +{ + Updated = 0, //!< Group object was updated + ReadRequest = 1, //!< Read was requested but was not processed + WriteRequest = 2, //!< Write was requested but was not processed + Transmitting = 3, //!< Group Object is processed a the moment (read or write) + Ok = 4, //!< read or write request were send successfully + Error = 5, //!< there was an error on processing a request + Uninitialized = 6 //!< uninitialized Group Object, its value is not valid +}; + +class GroupObject; + +#ifndef HAS_FUNCTIONAL + #if defined(__linux__) || defined(ARDUINO_ARCH_ESP32) || defined(ESP_PLATFORM) || defined(ARDUINO_ARCH_STM32) || defined (ARDUINO_ARCH_SAMD) || defined (ARDUINO_ARCH_RP2040) || defined(LIBRETINY) + #define HAS_FUNCTIONAL 1 + #else + #define HAS_FUNCTIONAL 0 + #endif +#endif + +#if HAS_FUNCTIONAL + #include + typedef std::function GroupObjectUpdatedHandler; +#else + typedef void (*GroupObjectUpdatedHandler)(GroupObject& go); +#endif + +/** + * This class represents a single group object. In german they are called "Kommunikationsobjekt" or "KO". + */ +class GroupObject +{ + friend class GroupObjectTableObject; + GroupObject(const GroupObject& other) = delete; + public: + /** + * The constructor. + */ + GroupObject(); + /** + * The destructor. + */ + virtual ~GroupObject(); + // config flags from ETS + /** + * Check if the update flag (U) was set. (A-flag in german) + */ + bool responseUpdateEnable(); + /** + * Check if the transmit flag (T) was set. (UE-flag in german) + */ + bool transmitEnable(); + /** + * Check if the initialisation flag (I) was set. + */ + bool valueReadOnInit(); + /** + * Check if the write flag (W) was set. (S-flag in german) + */ + bool writeEnable(); + /** + * Check if the read flag (R) was set. (L-flag in german) + */ + bool readEnable(); + /** + * Check if the communication flag (C) was set. (K-flag in german) + */ + bool communicationEnable(); + + /** + * Get the priority of the group object. + */ + Priority priority(); + + /** + * Return the current state of the group object. See ::ComFlag + */ + ComFlag commFlag(); + /** + * Set the current state of the group object. Application code should only use this to set the state to ::Ok after + * reading a ::Updated to mark the changed group object as processed. This is optional. + */ + void commFlag(ComFlag value); + + /** + * Check if the group object contains a valid value assigned from bus or from application program + */ + bool initialized(); + + /** + * Request the read of a communication object. Calling this function triggers the + * sending of a read-group-value telegram, to read the value of the communication + * object from the bus. + * + * When the answer is received, the communication object's value will be updated. + * + * This sets the state of the group objecte to ::ReadRequest + */ + void requestObjectRead(); + /** + * Mark a communication object as written. Calling this + * function triggers the sending of a write-group-value telegram. + * + * This sets the state of the group object to ::WriteRequest + */ + void objectWritten(); + + /** + * returns the size of the group object in Byte. For Group objects with size smaller than 1 byte (for example Dpt 1) this method + * will return 1. + */ + size_t valueSize(); + /** + * returns the size of the group object in Byte as it is in a telegram. For Group objects with size smaller than 1 byte (for example Dpt 1) this method + * will return 0. + */ + size_t sizeInTelegram(); + /** + * returns the size of the group object in the heap memory of the group object. The function returns the same value as goSize(), + * exept fot the 14 byte string type to reserve one byte of a \0 terminator character. + */ + size_t sizeInMemory() const; + /** + * returns the pointer to the value of the group object. This can be used if a datapoint type is not supported or if you want do + * your own conversion. + */ + uint8_t* valueRef(); + /** + * returns the Application Service Access Point of the group object. In reality this is just the number of the group object. + * (in german "KO-Nr") + */ + uint16_t asap(); + +#ifndef SMALL_GROUPOBJECT + /** + * register a callback for this group object. The registered callback will be called if the group object was changed from the bus. + */ + void callback(GroupObjectUpdatedHandler handler); + /** + * returns the registered callback + */ + GroupObjectUpdatedHandler callback(); +#endif + /** + * return the current value of the group object. + * @param type the datapoint type used for the conversion. If this doesn't fit to the group object the returned value is invalid. + */ + KNXValue value(const Dpt& type); + /** + * set the current value of the group object and changes the state of the group object to ::WriteRequest. + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value was converted successfully to the datapoint type and the group object was updated. + */ + bool value(const KNXValue& value, const Dpt& type); + + /** + * Check if the value (after conversion to dpt) will differ from current value of the group object and changes the state of the group object to ::WriteRequest if different. + * Use this method only, when the value should not be sent if it was not changed, otherwise value(const KNXValue&, const Dpt&) will do the same (without overhead for comparing) + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value of the group object has changed, false if conversion results in same value as stored in group object or failed. + */ + bool valueCompare(const KNXValue& value, const Dpt& type); + + /** + * set the current value of the group objectand show success. + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if value was converted successfully to the datapoint type and the group object was updated. + */ + bool valueNoSend(const KNXValue& value, const Dpt& type); + + /** + * Check if the value (after conversion to dpt) will differ from current value of the group object and update if necessary. + * Use this method only, when the value change is relevant, otherwise valueNoSend(const KNXValue&, const Dpt&) will do the same (without overhead for comparing) + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value of the group object has changed, false if conversion results in same value as stored in group object or failed. + */ + bool valueNoSendCompare(const KNXValue& value, const Dpt& type); + + /** + * set the current value of the group object. + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value of the group object was changed successfully. + */ + bool tryValue(KNXValue& value, const Dpt& type); + +#ifndef SMALL_GROUPOBJECT + /** + * return the current value of the group object. The datapoint type must be set with dataPointType(). Otherwise the returned + * value is invalid. + */ + KNXValue value(); + /** + * set the current value of the group object and changes the state of the group object to ::WriteRequest. + * @param value the value the group object is set to + * + * The parameters must fit the group object and dhe datapoint type must be set with dataPointType(). Otherwise it will stay unchanged. + * + * @returns true if the value was converted successfully to the datapoint type and the group object was updated. + */ + bool value(const KNXValue& value); + /** + * set the current value of the group object. + * @param value the value the group object is set to + * + * The parameters must fit the group object and the datapoint type must be set with dataPointType(). Otherwise it will stay unchanged. + * + * @returns true if the value was converted successfully to the datapoint type and the group object was updated. + */ + bool valueNoSend(const KNXValue& value); + /** + * set the current value of the group object. + * @param value the value the group object is set to + * + * The parameters must fit the group object and dhe datapoint type must be set with dataPointType(). Otherwise it will stay unchanged. + * + * @returns true if the value of the group object was changed successfully. + */ + bool tryValue(KNXValue& value); + + /** + * returns the currently configured datapoint type. + */ + Dpt dataPointType(); + /** + * sets the datapoint type of the group object. + */ + void dataPointType(Dpt value); +#else + /** + * Alternative callback processing: register one global callback for all group object. + * The registered callback will be called if any group object was changed from the bus. + * The callback method has to dispatch to the correct handler for this group object. + */ + static GroupObjectUpdatedHandler classCallback(); + static void classCallback(GroupObjectUpdatedHandler handler); + static void processClassCallback(GroupObject& ko); +#endif + + private: + // class members + static GroupObjectTableObject* _table; +#ifdef SMALL_GROUPOBJECT + static GroupObjectUpdatedHandler _updateHandlerStatic; +#endif + + size_t asapValueSize(uint8_t code) const; + size_t goSize(); + uint16_t _asap = 0; + bool _uninitialized : 1; + ComFlag _commFlag : 7; + uint8_t* _data = 0; + uint8_t _dataLength = 0; +#ifndef SMALL_GROUPOBJECT + GroupObjectUpdatedHandler _updateHandler; + Dpt _datapointType; +#endif +}; diff --git a/components/knx/src/knx/group_object_table_object.cpp b/components/knx/src/knx/group_object_table_object.cpp new file mode 100644 index 0000000..13d8123 --- /dev/null +++ b/components/knx/src/knx/group_object_table_object.cpp @@ -0,0 +1,131 @@ +#include + +#include "group_object_table_object.h" +#include "group_object.h" +#include "bits.h" +#include "data_property.h" + +GroupObjectTableObject::GroupObjectTableObject(Memory& memory) + : TableObject(memory) +{ + Property* properties[] + { + new DataProperty(PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_GRP_OBJ_TABLE) + }; + TableObject::initializeProperties(sizeof(properties), properties); +} + +GroupObjectTableObject::~GroupObjectTableObject() +{ + freeGroupObjects(); +} + +uint16_t GroupObjectTableObject::entryCount() +{ + if (loadState() != LS_LOADED) + return 0; + + return ntohs(_tableData[0]); +} + +GroupObject& GroupObjectTableObject::get(uint16_t asap) +{ + return _groupObjects[asap - 1]; +} + +const uint8_t* GroupObjectTableObject::restore(const uint8_t* buffer) +{ + buffer = TableObject::restore(buffer); + + _tableData = (uint16_t*)data(); + initGroupObjects(); + + return buffer; +} + +GroupObject& GroupObjectTableObject::nextUpdatedObject(bool& valid) +{ + static uint16_t startIdx = 1; + + uint16_t objCount = entryCount(); + + for (uint16_t asap = startIdx; asap <= objCount; asap++) + { + GroupObject& go = get(asap); + + if (go.commFlag() == Updated) + { + go.commFlag(Ok); + startIdx = asap + 1; + valid = true; + return go; + } + } + + startIdx = 1; + valid = false; + return get(1); +} + +void GroupObjectTableObject::groupObjects(GroupObject* objs, uint16_t size) +{ + freeGroupObjects(); + _groupObjects = objs; + _groupObjectCount = size; + initGroupObjects(); +} + +void GroupObjectTableObject::beforeStateChange(LoadState& newState) +{ + TableObject::beforeStateChange(newState); + + if (newState != LS_LOADED) + return; + + _tableData = (uint16_t*)data(); + + if (!initGroupObjects()) + { + newState = LS_ERROR; + TableObject::errorCode(E_SOFTWARE_FAULT); + } +} + +bool GroupObjectTableObject::initGroupObjects() +{ + if (!_tableData) + return false; + + freeGroupObjects(); + + uint16_t goCount = ntohs(_tableData[0]); + + _groupObjects = new GroupObject[goCount]; + _groupObjectCount = goCount; + + for (uint16_t asap = 1; asap <= goCount; asap++) + { + GroupObject& go = _groupObjects[asap - 1]; + go._asap = asap; + go._table = this; + + go._dataLength = go.goSize(); + size_t sizeInMemory = go.sizeInMemory(); + go._data = new uint8_t[sizeInMemory]; + memset(go._data, 0, sizeInMemory); + + if (go.valueReadOnInit()) + go.requestObjectRead(); + } + + return true; +} + +void GroupObjectTableObject::freeGroupObjects() +{ + if (_groupObjects) + delete[] _groupObjects; + + _groupObjectCount = 0; + _groupObjects = 0; +} diff --git a/components/knx/src/knx/group_object_table_object.h b/components/knx/src/knx/group_object_table_object.h new file mode 100644 index 0000000..fc5d131 --- /dev/null +++ b/components/knx/src/knx/group_object_table_object.h @@ -0,0 +1,29 @@ +#pragma once + +#include "table_object.h" +#include "group_object.h" + +class GroupObjectTableObject : public TableObject +{ + friend class GroupObject; + + public: + GroupObjectTableObject(Memory& memory); + virtual ~GroupObjectTableObject(); + uint16_t entryCount(); + GroupObject& get(uint16_t asap); + GroupObject& nextUpdatedObject(bool& valid); + void groupObjects(GroupObject* objs, uint16_t size); + + const uint8_t* restore(const uint8_t* buffer) override; + + protected: + void beforeStateChange(LoadState& newState) override; + + private: + void freeGroupObjects(); + bool initGroupObjects(); + uint16_t* _tableData = 0; + GroupObject* _groupObjects = 0; + uint16_t _groupObjectCount = 0; +}; \ No newline at end of file diff --git a/components/knx/src/knx/interface_object.cpp b/components/knx/src/knx/interface_object.cpp new file mode 100644 index 0000000..38969d0 --- /dev/null +++ b/components/knx/src/knx/interface_object.cpp @@ -0,0 +1,232 @@ +#include + +#include "interface_object.h" +#include "data_property.h" + +InterfaceObject::~InterfaceObject() +{ + if (_properties != nullptr) + delete[] _properties; +} + +void InterfaceObject::readPropertyDescription(uint8_t& propertyId, uint8_t& propertyIndex, bool& writeEnable, uint8_t& type, uint16_t& numberOfElements, uint8_t& access) +{ + uint8_t count = _propertyCount; + + numberOfElements = 0; + + if (_properties == nullptr || count == 0) + return; + + Property* prop = nullptr; + + // from KNX spec. 03.03.07 Application Layer (page 56) - 3.4.3.3 A_PropertyDescription_Read-service + // Summary: either propertyId OR propertyIndex, but not both at the same time + if (propertyId != 0) + { + for (uint8_t i = 0; i < count; i++) + { + Property* p = _properties[i]; + + if (p->Id() != propertyId) + continue; + + prop = p; + propertyIndex = i; + break; + } + } + else + { + // If propertyId is zero, propertyIndex shall be used. + // Response: propertyIndex of received A_PropertyDescription_Read + if (propertyIndex < count) + { + prop = _properties[propertyIndex]; + } + } + + if (prop != nullptr) + { + propertyId = prop->Id(); + writeEnable = prop->WriteEnable(); + type = prop->Type(); + numberOfElements = prop->MaxElements(); + access = prop->Access(); + } +} + +void InterfaceObject::masterReset(EraseCode eraseCode, uint8_t channel) +{ + // every interface object shall implement this + // However, for the time being we provide an empty default implementation +} + +void InterfaceObject::readPropertyLength(PropertyID id, uint16_t& length) +{ + uint8_t count = 1; + uint16_t propval = 0; + readProperty(id, 0, count, (uint8_t*)&propval); + + if (count == 0) + { + length = 0; + return; + } + + length = ntohs(propval); +} + +void InterfaceObject::readProperty(PropertyID id, uint16_t start, uint8_t& count, uint8_t* data) +{ + Property* prop = property(id); + + if (prop == nullptr) + { + count = 0; + return; + } + + count = prop->read(start, count, data); +} + +void InterfaceObject::writeProperty(PropertyID id, uint16_t start, uint8_t* data, uint8_t& count) +{ + Property* prop = property(id); + + if (prop == nullptr) + { + count = 0; + return; + } + + count = prop->write(start, count, data); +} + +uint8_t InterfaceObject::propertySize(PropertyID id) +{ + Property* prop = property(id); + + if (prop == nullptr) + { + return 0; + } + + return prop->ElementSize(); +} + +void InterfaceObject::command(PropertyID id, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) +{ + Property* prop = property(id); + + if (prop == nullptr) + { + resultLength = 0; + return; + } + + prop->command(data, length, resultData, resultLength); +} + +void InterfaceObject::state(PropertyID id, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) +{ + Property* prop = property(id); + + if (prop == nullptr) + { + resultLength = 0; + return; + } + + prop->state(data, length, resultData, resultLength); +} + +void InterfaceObject::initializeProperties(size_t propertiesSize, Property** properties) +{ + _propertyCount = propertiesSize / sizeof(Property*); + _properties = new Property*[_propertyCount]; + memcpy(_properties, properties, propertiesSize); +} + + +Property* InterfaceObject::property(PropertyID id) +{ + for (int i = 0; i < _propertyCount; i++) + if (_properties[i]->Id() == id) + return _properties[i]; + + return nullptr; +} + + +uint8_t* InterfaceObject::save(uint8_t* buffer) +{ + for (int i = 0; i < _propertyCount; i++) + { + Property* prop = _properties[i]; + + if (!prop->WriteEnable()) + continue; + + buffer = prop->save(buffer); + } + + return buffer; +} + + +const uint8_t* InterfaceObject::restore(const uint8_t* buffer) +{ + for (int i = 0; i < _propertyCount; i++) + { + Property* prop = _properties[i]; + + if (!prop->WriteEnable()) + continue; + + buffer = prop->restore(buffer); + } + + return buffer; +} + + +uint16_t InterfaceObject::saveSize() +{ + uint16_t size = 0; + + for (int i = 0; i < _propertyCount; i++) + { + Property* prop = _properties[i]; + + if (!prop->WriteEnable()) + continue; + + size += prop->saveSize(); + } + + return size; +} + + +const Property* InterfaceObject::property(PropertyID id) const +{ + for (int i = 0; i < _propertyCount; i++) + if (_properties[i]->Id() == id) + return _properties[i]; + + return nullptr; +} + + +const uint8_t* InterfaceObject::propertyData(PropertyID id) +{ + DataProperty* prop = (DataProperty*)property(id); + return prop->data(); +} + +const uint8_t* InterfaceObject::propertyData(PropertyID id, uint16_t elementIndex) +{ + DataProperty* prop = (DataProperty*)property(id); + return prop->data(elementIndex); +} diff --git a/components/knx/src/knx/interface_object.h b/components/knx/src/knx/interface_object.h new file mode 100644 index 0000000..fe9a371 --- /dev/null +++ b/components/knx/src/knx/interface_object.h @@ -0,0 +1,210 @@ +#pragma once + +#include +#include "property.h" +#include "save_restore.h" +#include "knx_types.h" +#include "bits.h" + +/** Enum for the type of an interface object. See Section 2.2 of knx:3/7/3 */ +enum ObjectType +{ + /** Device object. */ + OT_DEVICE = 0, + + /** Address table object. */ + OT_ADDR_TABLE = 1, + + /** Association table object. */ + OT_ASSOC_TABLE = 2, + + /** Application program object. */ + OT_APPLICATION_PROG = 3, + + /** Interface program object. */ + OT_INTERFACE_PROG = 4, + + /** KNX - Object Associationtable. */ + OT_OJB_ASSOC_TABLE = 5, + + /** Router Object */ + OT_ROUTER = 6, + + /** LTE Address Routing Table Object */ + OT_LTE_ADDR_ROUTING_TABLE = 7, + + /** cEMI Server Object */ + OT_CEMI_SERVER = 8, + + /** Group Object Table Object */ + OT_GRP_OBJ_TABLE = 9, + + /** Polling Master */ + OT_POLLING_MASTER = 10, + + /** KNXnet/IP Parameter Object */ + OT_IP_PARAMETER = 11, + + /** Reserved. Shall not be used. */ + OT_RESERVED = 12, + + /** File Server Object */ + OT_FILE_SERVER = 13, + + /** Security Interface Object */ + OT_SECURITY = 17, + + /** RF Medium Object */ + OT_RF_MEDIUM = 19, + + /** Dummy so this enum is 16bit */ + OT_DUMMY = 0xFFFF +}; + +/** + * This class represents and interface object. See section 4 of @cite knx:3/4/1. + */ +class InterfaceObject : public SaveRestore +{ + public: + /** + * Destructor + */ + virtual ~InterfaceObject(); + /** + * Read length of a property of the interface object. See section 4.8.4.2 of @cite knx:3/4/1. + * + * @param id id of the property to read + * + * @param[out] length length of the requested property + */ + virtual void readPropertyLength(PropertyID id, uint16_t& length); + /** + * Read a property of the interface object. See section 4.8.4.2 of @cite knx:3/4/1. + * + * @param id id of the property to read + * + * @param start (for properties with multiple values) at which element should we start + * + * @param[in, out] count how many values should be read. If there is a problem (e.g. property does not exist) + * this value is set to 0. + * + * @param[out] data The requested data of the property. + */ + virtual void readProperty(PropertyID id, uint16_t start, uint8_t& count, uint8_t* data); + /** + * Write property of the interface object. If the interface object does not have the property this + * method does nothing. See section 4.8.4.4 of @cite knx:3/4/1. + * + * @param id id of the property to write + * + * @param start (for properties with multiple values) at which element should we start + * + * @param[in, out] count how many values should be written. If there is a problem (e.g. property does not exist) + * this value is set to 0. + * + * @param[in] data The data that should be written. + */ + virtual void writeProperty(PropertyID id, uint16_t start, uint8_t* data, uint8_t& count); + /** + * Gets the size of of property in bytes. + * + * @param id of the property to get the size of + * + * @returns the size in byte or 0 if the interface object does not have the property + */ + virtual uint8_t propertySize(PropertyID id); + /** + * Call command of a function property of the interface object. Property type must be PDT_FUNCTION + * + * @param id id of the property to call + * + * @param[in] length The size of the data buffer + * + * @param[in] data The argument data for the function + * + * @param[out] resultLength The size of the result data buffer + * + * @param[out] resultData The result data for the function + */ + virtual void command(PropertyID id, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength); + /** + * Get state of a function property of the interface object. Property type must be PDT_FUNCTION + * + * @param id id of the property to call + * + * @param[in] length The size of the data buffer + * + * @param[in] data The argument data for the function + * + * @param[out] resultLength The size of the result data buffer + * + * @param[out] resultData The result data for the function + */ + virtual void state(PropertyID id, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength); + /** + * Read the Description of a property of the interface object. The output parameters are only valid if nuberOfElements is not zero. + * + * @param[in,out] propertyId The id of the property of which to read the description of. If this parameter is not zero + * propertyIndex paramter is ignored as input and the corrrect index of the property is written to it. If this + * parameter is zero the ::PropertyID of the property specified by propertyIndex is written to it. + * + * @param[in,out] propertyIndex The index of the property of the interface object of which to read the description of. + * only used for input if propertyId is not set. Otherwise the index of the property specified by propertyId is written to it. + * + * @param[out] writeEnable Can the property be written to. + * + * @param[out] type the ::PropertyDataType of the property + * + * @param[out] numberOfElements the number of elements of the property. Zero if the interface object does not have the requested property. + * + * @param[out] access the ::AccessLevel necessary to read/write the property. + */ + void readPropertyDescription(uint8_t& propertyId, uint8_t& propertyIndex, bool& writeEnable, uint8_t& type, uint16_t& numberOfElements, uint8_t& access); + + // every interface object shall implement this + // However, for the time being we provide an empty default implementation + virtual void masterReset(EraseCode eraseCode, uint8_t channel); + + /** + * Gets property with PropertyID id if it exists and nullptr otherwise. + */ + Property* property(PropertyID id); + + template + T propertyValue(PropertyID id) + { + const Property* prop = property(id); + + T value = 0; + prop->read(value); + return value; + } + + template + void propertyValue(PropertyID id, T value) + { + Property* prop = property(id); + prop->write(value); + } + + const uint8_t* propertyData(PropertyID id); + const uint8_t* propertyData(PropertyID id, uint16_t elementIndex); + /** + * Gets const property with PropertyID id if it exists and nullptr otherwise. + */ + const Property* property(PropertyID id) const; + + uint8_t* save(uint8_t* buffer) override; + const uint8_t* restore(const uint8_t* buffer) override; + uint16_t saveSize() override; + + protected: + /** + * Intializes the Property-array the the supplied values. + */ + virtual void initializeProperties(size_t propertiesSize, Property** properties); + + Property** _properties = nullptr; + uint8_t _propertyCount = 0; +}; diff --git a/components/knx/src/knx/ip_data_link_layer.cpp b/components/knx/src/knx/ip_data_link_layer.cpp new file mode 100644 index 0000000..461350c --- /dev/null +++ b/components/knx/src/knx/ip_data_link_layer.cpp @@ -0,0 +1,1191 @@ +#include "config.h" +#ifdef USE_IP + +#include "ip_data_link_layer.h" + +#include "bits.h" +#include "platform.h" +#include "device_object.h" +#include "knx_ip_routing_indication.h" +#include "knx_ip_search_request.h" +#include "knx_ip_search_response.h" +#include "knx_ip_search_request_extended.h" +#include "knx_ip_search_response_extended.h" +#include "knx_facade.h" +#ifdef KNX_TUNNELING + #include "knx_ip_connect_request.h" + #include "knx_ip_connect_response.h" + #include "knx_ip_state_request.h" + #include "knx_ip_state_response.h" + #include "knx_ip_disconnect_request.h" + #include "knx_ip_disconnect_response.h" + #include "knx_ip_tunneling_request.h" + #include "knx_ip_tunneling_ack.h" + #include "knx_ip_description_request.h" + #include "knx_ip_description_response.h" + #include "knx_ip_config_request.h" +#endif + +#include +#include + +#define KNXIP_HEADER_LEN 0x6 +#define KNXIP_PROTOCOL_VERSION 0x10 + +#define MIN_LEN_CEMI 10 + +IpDataLinkLayer::IpDataLinkLayer(DeviceObject& devObj, IpParameterObject& ipParam, + NetworkLayerEntity& netLayerEntity, Platform& platform, DataLinkLayerCallbacks* dllcb) : DataLinkLayer(devObj, netLayerEntity, platform), _ipParameters(ipParam), _dllcb(dllcb) +{ +} + +bool IpDataLinkLayer::sendFrame(CemiFrame& frame) +{ + KnxIpRoutingIndication packet(frame); + + // only send 50 packet per second: see KNX 3.2.6 p.6 + if (isSendLimitReached()) + return false; + + bool success = sendBytes(packet.data(), packet.totalLength()); +#ifdef KNX_ACTIVITYCALLBACK + + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_SEND << KNX_ACTIVITYCALLBACK_DIR)); + +#endif + dataConReceived(frame, success); + return success; +} + +#ifdef KNX_TUNNELING +void IpDataLinkLayer::dataRequestToTunnel(CemiFrame& frame) +{ + if (frame.addressType() == AddressType::GroupAddress) + { + for (int i = 0; i < KNX_TUNNELING; i++) + if (tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress == frame.sourceAddress()) + sendFrameToTunnel(&tunnels[i], frame); + + //TODO check if source is from tunnel + return; + } + + KnxIpTunnelConnection* tun = nullptr; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].IndividualAddress == frame.sourceAddress()) + continue; + + if (tunnels[i].IndividualAddress == frame.destinationAddress()) + { + tun = &tunnels[i]; + break; + } + } + + if (tun == nullptr) + { + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].IsConfig) + { +#ifdef KNX_LOG_TUNNELING + println("Found config Channel"); +#endif + tun = &tunnels[i]; + break; + } + } + } + + if (tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Found no Tunnel for IA: "); + println(frame.destinationAddress(), 16); +#endif + return; + } + + sendFrameToTunnel(tun, frame); +} + +void IpDataLinkLayer::dataConfirmationToTunnel(CemiFrame& frame) +{ + if (frame.addressType() == AddressType::GroupAddress) + { + for (int i = 0; i < KNX_TUNNELING; i++) + if (tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress == frame.sourceAddress()) + sendFrameToTunnel(&tunnels[i], frame); + + //TODO check if source is from tunnel + return; + } + + KnxIpTunnelConnection* tun = nullptr; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].IndividualAddress == frame.destinationAddress()) + continue; + + if (tunnels[i].IndividualAddress == frame.sourceAddress()) + { + tun = &tunnels[i]; + break; + } + } + + if (tun == nullptr) + { + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].IsConfig) + { +#ifdef KNX_LOG_TUNNELING + println("Found config Channel"); +#endif + tun = &tunnels[i]; + break; + } + } + } + + if (tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Found no Tunnel for IA: "); + println(frame.destinationAddress(), 16); +#endif + return; + } + + sendFrameToTunnel(tun, frame); +} + +void IpDataLinkLayer::dataIndicationToTunnel(CemiFrame& frame) +{ + if (frame.addressType() == AddressType::GroupAddress) + { + for (int i = 0; i < KNX_TUNNELING; i++) + if (tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress != frame.sourceAddress()) + sendFrameToTunnel(&tunnels[i], frame); + + return; + } + + KnxIpTunnelConnection* tun = nullptr; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].ChannelId == 0 || tunnels[i].IndividualAddress == frame.sourceAddress()) + continue; + + if (tunnels[i].IndividualAddress == frame.destinationAddress()) + { + tun = &tunnels[i]; + break; + } + } + + if (tun == nullptr) + { + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].IsConfig) + { +#ifdef KNX_LOG_TUNNELING + println("Found config Channel"); +#endif + tun = &tunnels[i]; + break; + } + } + } + + if (tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Found no Tunnel for IA: "); + println(frame.destinationAddress(), 16); +#endif + return; + } + + sendFrameToTunnel(tun, frame); +} + +void IpDataLinkLayer::sendFrameToTunnel(KnxIpTunnelConnection* tunnel, CemiFrame& frame) +{ +#ifdef KNX_LOG_TUNNELING + print("Send to Channel: "); + println(tunnel->ChannelId, 16); +#endif + KnxIpTunnelingRequest req(frame); + req.connectionHeader().sequenceCounter(tunnel->SequenceCounter_S++); + req.connectionHeader().length(LEN_CH); + req.connectionHeader().channelId(tunnel->ChannelId); + + if (frame.messageCode() != L_data_req && frame.messageCode() != L_data_con && frame.messageCode() != L_data_ind) + req.serviceTypeIdentifier(DeviceConfigurationRequest); + + _platform.sendBytesUniCast(tunnel->IpAddress, tunnel->PortData, req.data(), req.totalLength()); +} + +bool IpDataLinkLayer::isTunnelAddress(uint16_t addr) +{ + if (addr == 0) + return false; // 0.0.0 is not a valid tunnel address and is used as default value + + for (int i = 0; i < KNX_TUNNELING; i++) + if (tunnels[i].IndividualAddress == addr) + return true; + + return false; +} + +bool IpDataLinkLayer::isSentToTunnel(uint16_t address, bool isGrpAddr) +{ + if (isGrpAddr) + { + for (int i = 0; i < KNX_TUNNELING; i++) + if (tunnels[i].ChannelId != 0) + return true; + + return false; + } + else + { + for (int i = 0; i < KNX_TUNNELING; i++) + if (tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress == address) + return true; + + return false; + } +} +#endif + +void IpDataLinkLayer::loop() +{ + if (!_enabled) + return; + +#ifdef KNX_TUNNELING + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].ChannelId != 0) + { + if (millis() - tunnels[i].lastHeartbeat > 120000) + { +#ifdef KNX_LOG_TUNNELING + print("Closed Tunnel 0x"); + print(tunnels[i].ChannelId, 16); + println(" due to no heartbeat in 2 minutes"); +#endif + KnxIpDisconnectRequest discReq; + discReq.channelId(tunnels[i].ChannelId); + discReq.hpaiCtrl().length(LEN_IPHPAI); + discReq.hpaiCtrl().code(IPV4_UDP); + discReq.hpaiCtrl().ipAddress(tunnels[i].IpAddress); + discReq.hpaiCtrl().ipPortNumber(tunnels[i].PortCtrl); + _platform.sendBytesUniCast(tunnels[i].IpAddress, tunnels[i].PortCtrl, discReq.data(), discReq.totalLength()); + tunnels[i].Reset(); + } + + break; + } + } + +#endif + + + uint8_t buffer[512]; + uint16_t remotePort = 0; + uint32_t remoteAddr = 0; + int len = _platform.readBytesMultiCast(buffer, 512, remoteAddr, remotePort); + + if (len <= 0) + return; + + if (len < KNXIP_HEADER_LEN) + return; + + if (buffer[0] != KNXIP_HEADER_LEN + || buffer[1] != KNXIP_PROTOCOL_VERSION) + return; + +#ifdef KNX_ACTIVITYCALLBACK + + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_RECV << KNX_ACTIVITYCALLBACK_DIR)); + +#endif + + uint16_t code; + popWord(code, buffer + 2); + + switch ((KnxIpServiceType)code) + { + case RoutingIndication: + { + KnxIpRoutingIndication routingIndication(buffer, len); + frameReceived(routingIndication.frame()); + break; + } + + case SearchRequest: + { + KnxIpSearchRequest searchRequest(buffer, len); + KnxIpSearchResponse searchResponse(_ipParameters, _deviceObject); + + auto hpai = searchRequest.hpai(); +#ifdef KNX_ACTIVITYCALLBACK + + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_SEND << KNX_ACTIVITYCALLBACK_DIR) | (KNX_ACTIVITYCALLBACK_IPUNICAST)); + +#endif + _platform.sendBytesUniCast(hpai.ipAddress(), hpai.ipPortNumber(), searchResponse.data(), searchResponse.totalLength()); + break; + } + + case SearchRequestExt: + { +#if KNX_SERVICE_FAMILY_CORE >= 2 + loopHandleSearchRequestExtended(buffer, len); +#endif + break; + } + +#ifdef KNX_TUNNELING + + case ConnectRequest: + { + loopHandleConnectRequest(buffer, len, remoteAddr, remotePort); + break; + } + + case ConnectionStateRequest: + { + loopHandleConnectionStateRequest(buffer, len); + break; + } + + case DisconnectRequest: + { + loopHandleDisconnectRequest(buffer, len); + break; + } + + case DescriptionRequest: + { + loopHandleDescriptionRequest(buffer, len); + break; + } + + case DeviceConfigurationRequest: + { + loopHandleDeviceConfigurationRequest(buffer, len); + break; + } + + case TunnelingRequest: + { + loopHandleTunnelingRequest(buffer, len); + return; + } + + case DeviceConfigurationAck: + { + //TOOD nothing to do now + //println("got Ack"); + break; + } + + case TunnelingAck: + { + //TOOD nothing to do now + //println("got Ack"); + break; + } + +#endif + + default: + print("Unhandled service identifier: "); + println(code, HEX); + break; + } +} + +#if KNX_SERVICE_FAMILY_CORE >= 2 +void IpDataLinkLayer::loopHandleSearchRequestExtended(uint8_t* buffer, uint16_t length) +{ + KnxIpSearchRequestExtended searchRequest(buffer, length); + + if (searchRequest.srpByProgMode) + { + println("srpByProgMode"); + + if (!knx.progMode()) + return; + } + + if (searchRequest.srpByMacAddr) + { + println("srpByMacAddr"); + const uint8_t* x = _ipParameters.propertyData(PID_MAC_ADDRESS); + + for (int i = 0; i < 6; i++) + if (searchRequest.srpMacAddr[i] != x[i]) + return; + } + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#endif +#else +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) +#endif +#endif + + //defaults: "Device Information DIB", "Extended Device Information DIB" and "Supported Services DIB". + int dibLength = LEN_DEVICE_INFORMATION_DIB + LEN_SERVICE_DIB + LEN_EXTENDED_DEVICE_INFORMATION_DIB; + + if (searchRequest.srpByService) + { + println("srpByService"); + uint8_t length = searchRequest.srpServiceFamilies[0]; + uint8_t* currentPos = searchRequest.srpServiceFamilies + 2; + + for (int i = 0; i < (length - 2) / 2; i++) + { + uint8_t serviceFamily = (currentPos + i * 2)[0]; + uint8_t version = (currentPos + i * 2)[1]; + + switch (serviceFamily) + { + case Core: + if (version > KNX_SERVICE_FAMILY_CORE) + return; + + break; + + case DeviceManagement: + if (version > KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT) + return; + + break; + + case Tunnelling: + if (version > KNX_SERVICE_FAMILY_TUNNELING) + return; + + break; + + case Routing: + if (version > KNX_SERVICE_FAMILY_ROUTING) + return; + + break; + } + } + } + + if (searchRequest.srpRequestDIBs) + { + //println("srpRequestDIBs"); + if(searchRequest.requestedDIB(IP_CONFIG)) + dibLength += LEN_IP_CONFIG_DIB; //16 + + if (searchRequest.requestedDIB(IP_CUR_CONFIG)) + dibLength += LEN_IP_CURRENT_CONFIG_DIB; //20 + + if (searchRequest.requestedDIB(KNX_ADDRESSES)) + { + uint16_t length = 0; + _ipParameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + dibLength += 4 + length * 2; + } + + if (searchRequest.requestedDIB(MANUFACTURER_DATA)) + dibLength += 0; //4 + n + + if (searchRequest.requestedDIB(TUNNELING_INFO)) + { + uint16_t length = 0; + _ipParameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + dibLength += 4 + length * 4; + } + } + + KnxIpSearchResponseExtended searchResponse(_ipParameters, _deviceObject, dibLength); + + searchResponse.setDeviceInfo(_ipParameters, _deviceObject); //DescriptionTypeCode::DeviceInfo 1 + searchResponse.setSupportedServices(); //DescriptionTypeCode::SUPP_SVC_FAMILIES 2 + searchResponse.setExtendedDeviceInfo(); //DescriptionTypeCode::EXTENDED_DEVICE_INFO 8 + + if (searchRequest.srpRequestDIBs) + { + if (searchRequest.requestedDIB(IP_CONFIG)) + searchResponse.setIpConfig(_ipParameters); + + if (searchRequest.requestedDIB(IP_CUR_CONFIG)) + searchResponse.setIpCurrentConfig(_ipParameters); + + if (searchRequest.requestedDIB(KNX_ADDRESSES)) + searchResponse.setKnxAddresses(_ipParameters, _deviceObject); + + if (searchRequest.requestedDIB(MANUFACTURER_DATA)) + { + //println("requested MANUFACTURER_DATA but not implemented"); + } + + if (searchRequest.requestedDIB(TUNNELING_INFO)) + searchResponse.setTunnelingInfo(_ipParameters, _deviceObject, tunnels); + } + + if(searchResponse.totalLength() > 500) + { + printf("skipped response length > 500. Length: %d bytes\n", searchResponse.totalLength()); + return; + } + + _platform.sendBytesUniCast(searchRequest.hpai().ipAddress(), searchRequest.hpai().ipPortNumber(), searchResponse.data(), searchResponse.totalLength()); +} +#endif + +#ifdef KNX_TUNNELING +void IpDataLinkLayer::loopHandleConnectRequest(uint8_t* buffer, uint16_t length, uint32_t& src_addr, uint16_t& src_port) +{ + KnxIpConnectRequest connRequest(buffer, length); +#ifdef KNX_LOG_TUNNELING + println("Got Connect Request!"); + + switch (connRequest.cri().type()) + { + case DEVICE_MGMT_CONNECTION: + println("Device Management Connection"); + break; + + case TUNNEL_CONNECTION: + println("Tunnel Connection"); + break; + + case REMLOG_CONNECTION: + println("RemLog Connection"); + break; + + case REMCONF_CONNECTION: + println("RemConf Connection"); + break; + + case OBJSVR_CONNECTION: + println("ObjectServer Connection"); + break; + } + + print("Data Endpoint: "); + uint32_t ip = connRequest.hpaiData().ipAddress(); + print(ip >> 24); + print("."); + print((ip >> 16) & 0xFF); + print("."); + print((ip >> 8) & 0xFF); + print("."); + print(ip & 0xFF); + print(":"); + println(connRequest.hpaiData().ipPortNumber()); + print("Ctrl Endpoint: "); + ip = connRequest.hpaiCtrl().ipAddress(); + print(ip >> 24); + print("."); + print((ip >> 16) & 0xFF); + print("."); + print((ip >> 8) & 0xFF); + print("."); + print(ip & 0xFF); + print(":"); + println(connRequest.hpaiCtrl().ipPortNumber()); +#endif + + //We only support 0x03 and 0x04! + if (connRequest.cri().type() != TUNNEL_CONNECTION && connRequest.cri().type() != DEVICE_MGMT_CONNECTION) + { +#ifdef KNX_LOG_TUNNELING + println("Only Tunnel/DeviceMgmt Connection ist supported!"); +#endif + KnxIpConnectResponse connRes(0x00, E_CONNECTION_TYPE); + _platform.sendBytesUniCast(connRequest.hpaiCtrl().ipAddress(), connRequest.hpaiCtrl().ipPortNumber(), connRes.data(), connRes.totalLength()); + return; + } + + if (connRequest.cri().type() == TUNNEL_CONNECTION && connRequest.cri().layer() != 0x02) //LinkLayer + { + //We only support 0x02! +#ifdef KNX_LOG_TUNNELING + println("Only LinkLayer ist supported!"); +#endif + KnxIpConnectResponse connRes(0x00, E_TUNNELING_LAYER); + _platform.sendBytesUniCast(connRequest.hpaiCtrl().ipAddress(), connRequest.hpaiCtrl().ipPortNumber(), connRes.data(), connRes.totalLength()); + return; + } + + // data preparation + + uint32_t srcIP = connRequest.hpaiCtrl().ipAddress() ? connRequest.hpaiCtrl().ipAddress() : src_addr; + uint16_t srcPort = connRequest.hpaiCtrl().ipPortNumber() ? connRequest.hpaiCtrl().ipPortNumber() : src_port; + + // read current elements in PID_ADDITIONAL_INDIVIDUAL_ADDRESSES + uint16_t propCount = 0; + _ipParameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, propCount); + const uint8_t* addresses; + + if (propCount == KNX_TUNNELING) + { + addresses = _ipParameters.propertyData(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES); + } + else // no tunnel PA configured, that means device is unconfigured and has 15.15.0 + { + uint8_t addrbuffer[KNX_TUNNELING * 2]; + addresses = (uint8_t*)addrbuffer; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + addrbuffer[i * 2 + 1] = i + 1; + addrbuffer[i * 2] = _deviceObject.individualAddress() / 0x0100; + } + + uint8_t count = KNX_TUNNELING; + _ipParameters.writeProperty(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, 1, addrbuffer, count); +#ifdef KNX_LOG_TUNNELING + println("no Tunnel-PAs configured, using own subnet"); +#endif + } + + _ipParameters.readPropertyLength(PID_CUSTOM_RESERVED_TUNNELS_CTRL, propCount); + const uint8_t* tunCtrlBytes = nullptr; + + if (propCount == KNX_TUNNELING) + tunCtrlBytes = _ipParameters.propertyData(PID_CUSTOM_RESERVED_TUNNELS_CTRL); + + _ipParameters.readPropertyLength(PID_CUSTOM_RESERVED_TUNNELS_IP, propCount); + const uint8_t* tunCtrlIp = nullptr; + + if (propCount == KNX_TUNNELING) + tunCtrlIp = _ipParameters.propertyData(PID_CUSTOM_RESERVED_TUNNELS_IP); + + bool resTunActive = (tunCtrlBytes && tunCtrlIp); +#ifdef KNX_LOG_TUNNELING + + if (resTunActive) + println("Reserved Tunnel Feature active"); + + if (tunCtrlBytes) + printHex("tunCtrlBytes", tunCtrlBytes, KNX_TUNNELING); + + if (tunCtrlIp) + printHex("tunCtrlIp", tunCtrlIp, KNX_TUNNELING * 4); + +#endif + + // check if there is a reserved tunnel for the source + int firstFreeTunnel = -1; + int firstResAndFreeTunnel = -1; + int firstResAndOccTunnel = -1; + bool tunnelResActive[KNX_TUNNELING] = {0}; + uint8_t tunnelResOptions[KNX_TUNNELING] = {0}; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (resTunActive) + { + tunnelResActive[i] = *(tunCtrlBytes + i) & 0x80; + tunnelResOptions[i] = (*(tunCtrlBytes + i) & 0x60) >> 5; + } + + if(resTunActive && tunnelResActive[i]) // tunnel reserve feature active for this tunnel + { +#ifdef KNX_LOG_TUNNELING + print("tunnel reserve feature active for this tunnel: "); + print(tunnelResActive[i]); + print(" options: "); + println(tunnelResOptions[i]); +#endif + + uint32_t rIP = 0; + popInt(rIP, tunCtrlIp + 4 * i); + + if (srcIP == rIP && connRequest.cri().type() == TUNNEL_CONNECTION) + { + // reserved tunnel for this ip found + if (tunnels[i].ChannelId == 0) // check if it is free + { + if (firstResAndFreeTunnel < 0) + firstResAndFreeTunnel = i; + } + else + { + if (firstResAndOccTunnel < 0) + firstResAndOccTunnel = i; + } + } + } + else + { + if (tunnels[i].ChannelId == 0 && firstFreeTunnel < 0) + firstFreeTunnel = i; + } + } + +#ifdef KNX_LOG_TUNNELING + print("firstFreeTunnel: "); + print(firstFreeTunnel); + print(" firstResAndFreeTunnel: "); + print(firstResAndFreeTunnel); + print(" firstResAndOccTunnel: "); + println(firstResAndOccTunnel); +#endif + + + uint8_t tunIdx = 0xff; + + if (resTunActive & (firstResAndFreeTunnel >= 0 || firstResAndOccTunnel >= 0)) // tunnel reserve feature active (for this src) + { + if (firstResAndFreeTunnel >= 0) + { + tunIdx = firstResAndFreeTunnel; + } + else if (firstResAndOccTunnel >= 0) + { + if (tunnelResOptions[firstResAndOccTunnel] == 1) // decline req + { + ; // do nothing => decline + } + else if (tunnelResOptions[firstResAndOccTunnel] == 2) // close current tunnel connection on this tunnel and assign to this request + { + KnxIpDisconnectRequest discReq; + discReq.channelId(tunnels[firstResAndOccTunnel].ChannelId); + discReq.hpaiCtrl().length(LEN_IPHPAI); + discReq.hpaiCtrl().code(IPV4_UDP); + discReq.hpaiCtrl().ipAddress(tunnels[firstResAndOccTunnel].IpAddress); + discReq.hpaiCtrl().ipPortNumber(tunnels[firstResAndOccTunnel].PortCtrl); + _platform.sendBytesUniCast(tunnels[firstResAndOccTunnel].IpAddress, tunnels[firstResAndOccTunnel].PortCtrl, discReq.data(), discReq.totalLength()); + tunnels[firstResAndOccTunnel].Reset(); + + + tunIdx = firstResAndOccTunnel; + } + else if (tunnelResOptions[firstResAndOccTunnel] == 3) // use the first unreserved tunnel (if one) + { + if (firstFreeTunnel >= 0) + tunIdx = firstFreeTunnel; + else + ; // do nothing => decline + } + + //else + // should not happen + // do nothing => decline + } + + //else + // should not happen + // do nothing => decline + } + else + { + if (firstFreeTunnel >= 0) + tunIdx = firstFreeTunnel; + + //else + // do nothing => decline + } + + KnxIpTunnelConnection* tun = nullptr; + + if (tunIdx != 0xFF) + { + tun = &tunnels[tunIdx]; + + uint16_t tunPa = 0; + popWord(tunPa, addresses + (tunIdx * 2)); + + //check if this PA is in use (should not happen, only when there is one pa wrongly assigned to more then one tunnel) + for (int x = 0; x < KNX_TUNNELING; x++) + if (tunnels[x].IndividualAddress == tunPa) + { +#ifdef KNX_LOG_TUNNELING + println("cannot use tunnel because PA is already in use"); +#endif + tunIdx = 0xFF; + tun = nullptr; + break; + } + if(tun) + tun->IndividualAddress = tunPa; + + } + + if (tun == nullptr) + { + println("no free tunnel availible"); + KnxIpConnectResponse connRes(0x00, E_NO_MORE_CONNECTIONS); + _platform.sendBytesUniCast(connRequest.hpaiCtrl().ipAddress(), connRequest.hpaiCtrl().ipPortNumber(), connRes.data(), connRes.totalLength()); + return; + } + + if (connRequest.cri().type() == DEVICE_MGMT_CONNECTION) + tun->IsConfig = true; + + // the channel ID shall be unique on this tunnel server. catch the rare case of a double channel ID + bool channelIdInUse; + + do + { + _lastChannelId++; + channelIdInUse = false; + + for (int x = 0; x < KNX_TUNNELING; x++) + if (tunnels[x].ChannelId == _lastChannelId) + channelIdInUse = true; + } while (channelIdInUse); + + tun->ChannelId = _lastChannelId; + tun->lastHeartbeat = millis(); + + if (_lastChannelId == 255) + _lastChannelId = 0; + + tun->IpAddress = srcIP; + tun->PortData = connRequest.hpaiData().ipPortNumber()?connRequest.hpaiData().ipPortNumber():srcPort; + tun->PortCtrl = connRequest.hpaiCtrl().ipPortNumber()?connRequest.hpaiCtrl().ipPortNumber():srcPort; + + print("New Tunnel-Connection["); + print(tunIdx); + print("], Channel: 0x"); + print(tun->ChannelId, 16); + print(" PA: "); + print(tun->IndividualAddress >> 12); + print("."); + print((tun->IndividualAddress >> 8) & 0xF); + print("."); + print(tun->IndividualAddress & 0xFF); + + print(" with "); + print(tun->IpAddress >> 24); + print("."); + print((tun->IpAddress >> 16) & 0xFF); + print("."); + print((tun->IpAddress >> 8) & 0xFF); + print("."); + print(tun->IpAddress & 0xFF); + print(":"); + print(tun->PortData); + + if (tun->PortData != tun->PortCtrl) + { + print(" (Ctrlport: "); + print(tun->PortCtrl); + print(")"); + } + + if (tun->IsConfig) + { + print(" (Config-Channel)"); + } + + println(); + + + KnxIpConnectResponse connRes(_ipParameters, tun->IndividualAddress, 3671, tun->ChannelId, connRequest.cri().type()); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortCtrl, connRes.data(), connRes.totalLength()); +} + +void IpDataLinkLayer::loopHandleConnectionStateRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpStateRequest stateRequest(buffer, length); + + KnxIpTunnelConnection* tun = nullptr; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].ChannelId == stateRequest.channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if (tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Channel ID nicht gefunden: "); + println(stateRequest.channelId()); +#endif + KnxIpStateResponse stateRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(stateRequest.hpaiCtrl().ipAddress(), stateRequest.hpaiCtrl().ipPortNumber(), stateRes.data(), stateRes.totalLength()); + return; + } + + //TODO check knx connection! + //if no connection return E_KNX_CONNECTION + + //TODO check when to send E_DATA_CONNECTION + + tun->lastHeartbeat = millis(); + KnxIpStateResponse stateRes(tun->ChannelId, E_NO_ERROR); + _platform.sendBytesUniCast(stateRequest.hpaiCtrl().ipAddress(), stateRequest.hpaiCtrl().ipPortNumber(), stateRes.data(), stateRes.totalLength()); +} + +void IpDataLinkLayer::loopHandleDisconnectRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpDisconnectRequest discReq(buffer, length); + +#ifdef KNX_LOG_TUNNELING + print(">>> Disconnect Channel ID: "); + println(discReq.channelId()); +#endif + + KnxIpTunnelConnection* tun = nullptr; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].ChannelId == discReq.channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if (tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Channel ID nicht gefunden: "); + println(discReq.channelId()); +#endif + KnxIpDisconnectResponse discRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(discReq.hpaiCtrl().ipAddress(), discReq.hpaiCtrl().ipPortNumber(), discRes.data(), discRes.totalLength()); + return; + } + + + KnxIpDisconnectResponse discRes(tun->ChannelId, E_NO_ERROR); + _platform.sendBytesUniCast(discReq.hpaiCtrl().ipAddress(), discReq.hpaiCtrl().ipPortNumber(), discRes.data(), discRes.totalLength()); + tun->Reset(); +} + +void IpDataLinkLayer::loopHandleDescriptionRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpDescriptionRequest descReq(buffer, length); + KnxIpDescriptionResponse descRes(_ipParameters, _deviceObject); + _platform.sendBytesUniCast(descReq.hpaiCtrl().ipAddress(), descReq.hpaiCtrl().ipPortNumber(), descRes.data(), descRes.totalLength()); +} + +void IpDataLinkLayer::loopHandleDeviceConfigurationRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpConfigRequest confReq(buffer, length); + + KnxIpTunnelConnection* tun = nullptr; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].ChannelId == confReq.connectionHeader().channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if (tun == nullptr) + { + print("Channel ID nicht gefunden: "); + println(confReq.connectionHeader().channelId()); + KnxIpStateResponse stateRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(0, 0, stateRes.data(), stateRes.totalLength()); + return; + } + + KnxIpTunnelingAck tunnAck; + tunnAck.serviceTypeIdentifier(DeviceConfigurationAck); + tunnAck.connectionHeader().length(4); + tunnAck.connectionHeader().channelId(tun->ChannelId); + tunnAck.connectionHeader().sequenceCounter(confReq.connectionHeader().sequenceCounter()); + tunnAck.connectionHeader().status(E_NO_ERROR); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortData, tunnAck.data(), tunnAck.totalLength()); + + tun->lastHeartbeat = millis(); + _cemiServer->frameReceived(confReq.frame()); +} + +void IpDataLinkLayer::loopHandleTunnelingRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpTunnelingRequest tunnReq(buffer, length); + + KnxIpTunnelConnection* tun = nullptr; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].ChannelId == tunnReq.connectionHeader().channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if (tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Channel ID nicht gefunden: "); + println(tunnReq.connectionHeader().channelId()); +#endif + KnxIpStateResponse stateRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(0, 0, stateRes.data(), stateRes.totalLength()); + return; + } + + uint8_t sequence = tunnReq.connectionHeader().sequenceCounter(); + + if (sequence == tun->SequenceCounter_R) + { +#ifdef KNX_LOG_TUNNELING + print("Received SequenceCounter again: "); + println(tunnReq.connectionHeader().sequenceCounter()); +#endif + //we already got this one + //so just ack it + KnxIpTunnelingAck tunnAck; + tunnAck.connectionHeader().length(4); + tunnAck.connectionHeader().channelId(tun->ChannelId); + tunnAck.connectionHeader().sequenceCounter(tunnReq.connectionHeader().sequenceCounter()); + tunnAck.connectionHeader().status(E_NO_ERROR); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortData, tunnAck.data(), tunnAck.totalLength()); + return; + } + else if ((uint8_t)(sequence - 1) != tun->SequenceCounter_R) + { +#ifdef KNX_LOG_TUNNELING + print("Wrong SequenceCounter: got "); + print(tunnReq.connectionHeader().sequenceCounter()); + print(" expected "); + println((uint8_t)(tun->SequenceCounter_R + 1)); +#endif + //Dont handle it + return; + } + + KnxIpTunnelingAck tunnAck; + tunnAck.connectionHeader().length(4); + tunnAck.connectionHeader().channelId(tun->ChannelId); + tunnAck.connectionHeader().sequenceCounter(tunnReq.connectionHeader().sequenceCounter()); + tunnAck.connectionHeader().status(E_NO_ERROR); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortData, tunnAck.data(), tunnAck.totalLength()); + + tun->SequenceCounter_R = tunnReq.connectionHeader().sequenceCounter(); + + if (tunnReq.frame().sourceAddress() == 0) + tunnReq.frame().sourceAddress(tun->IndividualAddress); + + _cemiServer->frameReceived(tunnReq.frame()); +} +#endif + +void IpDataLinkLayer::enabled(bool value) +{ + // _print("own address: "); + // _println(_deviceObject.individualAddress()); + if (value && !_enabled) + { + _platform.setupMultiCast(_ipParameters.propertyValue(PID_ROUTING_MULTICAST_ADDRESS), KNXIP_MULTICAST_PORT); + _enabled = true; + return; + } + + if (!value && _enabled) + { + _platform.closeMultiCast(); + _enabled = false; + return; + } +} + +bool IpDataLinkLayer::enabled() const +{ + return _enabled; +} + +DptMedium IpDataLinkLayer::mediumType() const +{ + return DptMedium::KNX_IP; +} + +bool IpDataLinkLayer::sendBytes(uint8_t* bytes, uint16_t length) +{ + if (!_enabled) + return false; + + return _platform.sendBytesMultiCast(bytes, length); +} + +bool IpDataLinkLayer::isSendLimitReached() +{ + uint32_t curTime = millis() / 100; + + // check if the countbuffer must be adjusted + if (_frameCountTimeBase >= curTime) + { + uint32_t timeBaseDiff = _frameCountTimeBase - curTime; + + if (timeBaseDiff > 10) + timeBaseDiff = 10; + + for (uint32_t i = 0; i < timeBaseDiff ; i++) + { + _frameCountBase++; + _frameCountBase = _frameCountBase % 10; + _frameCount[_frameCountBase] = 0; + } + + _frameCountTimeBase = curTime; + } + else // _frameCountTimeBase < curTime => millis overflow, reset + { + for (int i = 0; i < 10 ; i++) + _frameCount[i] = 0; + + _frameCountBase = 0; + _frameCountTimeBase = curTime; + } + + //check if we are over the limit + uint16_t sum = 0; + + for (int i = 0; i < 10 ; i++) + sum += _frameCount[i]; + + if (sum > 50) + { + println("Dropping packet due to 50p/s limit"); + return true; // drop packet + } + else + { + _frameCount[_frameCountBase]++; + //print("sent packages in last 1000ms: "); + //print(sum); + //print(" curTime: "); + //println(curTime); + return false; + } +} +#endif diff --git a/components/knx/src/knx/ip_data_link_layer.h b/components/knx/src/knx/ip_data_link_layer.h new file mode 100644 index 0000000..d8e7227 --- /dev/null +++ b/components/knx/src/knx/ip_data_link_layer.h @@ -0,0 +1,59 @@ +#pragma once + +#include "config.h" +#ifdef USE_IP + +#include +#include "data_link_layer.h" +#include "ip_parameter_object.h" +#include "knx_ip_tunnel_connection.h" +#include "service_families.h" + +class IpDataLinkLayer : public DataLinkLayer +{ + using DataLinkLayer::_deviceObject; + + public: + IpDataLinkLayer(DeviceObject& devObj, IpParameterObject& ipParam, NetworkLayerEntity& netLayerEntity, + Platform& platform, DataLinkLayerCallbacks* dllcb = nullptr); + + void loop(); + void enabled(bool value); + bool enabled() const; + DptMedium mediumType() const override; +#ifdef KNX_TUNNELING + void dataRequestToTunnel(CemiFrame& frame) override; + void dataConfirmationToTunnel(CemiFrame& frame) override; + void dataIndicationToTunnel(CemiFrame& frame) override; + bool isTunnelAddress(uint16_t addr) override; + bool isSentToTunnel(uint16_t address, bool isGrpAddr); +#endif + + private: + bool _enabled = false; + uint8_t _frameCount[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t _frameCountBase = 0; + uint32_t _frameCountTimeBase = 0; + bool sendFrame(CemiFrame& frame); +#ifdef KNX_TUNNELING + void sendFrameToTunnel(KnxIpTunnelConnection* tunnel, CemiFrame& frame); + void loopHandleConnectRequest(uint8_t* buffer, uint16_t length, uint32_t& src_addr, uint16_t& src_port); + void loopHandleConnectionStateRequest(uint8_t* buffer, uint16_t length); + void loopHandleDisconnectRequest(uint8_t* buffer, uint16_t length); + void loopHandleDescriptionRequest(uint8_t* buffer, uint16_t length); + void loopHandleDeviceConfigurationRequest(uint8_t* buffer, uint16_t length); + void loopHandleTunnelingRequest(uint8_t* buffer, uint16_t length); +#endif +#if KNX_SERVICE_FAMILY_CORE >= 2 + void loopHandleSearchRequestExtended(uint8_t* buffer, uint16_t length); +#endif + bool sendBytes(uint8_t* buffer, uint16_t length); + bool isSendLimitReached(); + IpParameterObject& _ipParameters; + DataLinkLayerCallbacks* _dllcb; +#ifdef KNX_TUNNELING + KnxIpTunnelConnection tunnels[KNX_TUNNELING]; + uint8_t _lastChannelId = 0; +#endif +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/ip_host_protocol_address_information.cpp b/components/knx/src/knx/ip_host_protocol_address_information.cpp new file mode 100644 index 0000000..ed52814 --- /dev/null +++ b/components/knx/src/knx/ip_host_protocol_address_information.cpp @@ -0,0 +1,48 @@ +#include "ip_host_protocol_address_information.h" +#include "bits.h" +#ifdef USE_IP +IpHostProtocolAddressInformation::IpHostProtocolAddressInformation(uint8_t* data) + : _data(data) +{} + + +uint8_t IpHostProtocolAddressInformation::length() const +{ + return *_data; +} + +void IpHostProtocolAddressInformation::length(uint8_t value) +{ + *_data = value; +} + +HostProtocolCode IpHostProtocolAddressInformation::code() const +{ + return (HostProtocolCode)_data[1]; +} + +void IpHostProtocolAddressInformation::code(HostProtocolCode value) +{ + _data[1] = value; +} + +uint32_t IpHostProtocolAddressInformation::ipAddress() const +{ + return getInt(_data + 2); +} + +void IpHostProtocolAddressInformation::ipAddress(uint32_t value) +{ + pushInt(value, _data + 2); +} + +uint16_t IpHostProtocolAddressInformation::ipPortNumber() const +{ + return getWord(_data + 6); +} + +void IpHostProtocolAddressInformation::ipPortNumber(uint16_t value) +{ + pushWord(value, _data + 6); +} +#endif diff --git a/components/knx/src/knx/ip_host_protocol_address_information.h b/components/knx/src/knx/ip_host_protocol_address_information.h new file mode 100644 index 0000000..c46b1b2 --- /dev/null +++ b/components/knx/src/knx/ip_host_protocol_address_information.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "config.h" + +enum HostProtocolCode : uint8_t +{ + IPV4_UDP = 1, + IPV4_TCP = 2 +}; + +#ifdef USE_IP + +#define LEN_IPHPAI 8 +#define LEN_CRD 4 + +class IpHostProtocolAddressInformation +{ + public: + IpHostProtocolAddressInformation(uint8_t* data); + uint8_t length() const; + void length(uint8_t value); + HostProtocolCode code() const; + void code(HostProtocolCode value); + uint32_t ipAddress() const; + void ipAddress(uint32_t value); + uint16_t ipPortNumber() const; + void ipPortNumber(uint16_t value); + + private: + uint8_t* _data; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/ip_parameter_object.cpp b/components/knx/src/knx/ip_parameter_object.cpp new file mode 100644 index 0000000..a5644f9 --- /dev/null +++ b/components/knx/src/knx/ip_parameter_object.cpp @@ -0,0 +1,143 @@ +#include "ip_parameter_object.h" +#ifdef USE_IP +#include "device_object.h" +#include "platform.h" +#include "bits.h" +#include "data_property.h" +#include "callback_property.h" + +// 224.0.23.12 +#define DEFAULT_MULTICAST_ADDR ((uint32_t)0xE000170C) + +IpParameterObject::IpParameterObject(DeviceObject& deviceObject, Platform& platform): _deviceObject(deviceObject), + _platform(platform) +{ + Property* properties[] = + { + new DataProperty(PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_IP_PARAMETER), + new DataProperty(PID_PROJECT_INSTALLATION_ID, true, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv3), + new CallbackProperty(this, PID_KNX_INDIVIDUAL_ADDRESS, true, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv3, + [](IpParameterObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + // TODO: get property of deviceobject and use it + pushWord(io->_deviceObject.individualAddress(), data); + return 1; + }, + [](IpParameterObject * io, uint16_t start, uint8_t count, const uint8_t* data) -> uint8_t + { + io->_deviceObject.individualAddress(getWord(data)); + return 1; + }), +#ifdef KNX_TUNNELING + new DataProperty(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, true, PDT_UNSIGNED_INT, KNX_TUNNELING, ReadLv3 | WriteLv3), + new DataProperty(PID_CUSTOM_RESERVED_TUNNELS_CTRL, true, PDT_UNSIGNED_CHAR, KNX_TUNNELING, ReadLv3 | WriteLv3), // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) + new DataProperty(PID_CUSTOM_RESERVED_TUNNELS_IP, true, PDT_UNSIGNED_LONG, KNX_TUNNELING, ReadLv3 | WriteLv3), // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) +#endif + new DataProperty(PID_CURRENT_IP_ASSIGNMENT_METHOD, false, PDT_UNSIGNED_CHAR, 0, ReadLv3 | WriteLv3), + new DataProperty(PID_IP_ASSIGNMENT_METHOD, true, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv3), + new DataProperty(PID_IP_CAPABILITIES, true, PDT_BITSET8, 0, ReadLv3 | WriteLv1), // must be set by application due to capabilities of the used ip stack + new CallbackProperty(this, PID_CURRENT_IP_ADDRESS, false, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv0, + [](IpParameterObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + pushInt(htonl(io->_platform.currentIpAddress()), data); + return 1; + }), + new CallbackProperty(this, PID_CURRENT_SUBNET_MASK, false, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv0, + [](IpParameterObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + pushInt(htonl(io->_platform.currentSubnetMask()), data); + return 1; + }), + new CallbackProperty(this, PID_CURRENT_DEFAULT_GATEWAY, false, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv0, + [](IpParameterObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + pushInt(htonl(io->_platform.currentDefaultGateway()), data); + return 1; + }), + new DataProperty(PID_IP_ADDRESS, true, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv3), + new DataProperty(PID_SUBNET_MASK, true, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv3), + new DataProperty(PID_DEFAULT_GATEWAY, true, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv3), + new CallbackProperty(this, PID_MAC_ADDRESS, false, PDT_GENERIC_06, 1, ReadLv3 | WriteLv0, + [](IpParameterObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + io->_platform.macAddress(data); + return 1; + }), + new CallbackProperty(this, PID_SYSTEM_SETUP_MULTICAST_ADDRESS, false, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv0, + [](IpParameterObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + pushInt(DEFAULT_MULTICAST_ADDR, data); + return 1; + }), + new DataProperty(PID_ROUTING_MULTICAST_ADDRESS, true, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv3, DEFAULT_MULTICAST_ADDR), + new DataProperty(PID_TTL, true, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv3, (uint8_t)16), + new CallbackProperty(this, PID_KNXNETIP_DEVICE_CAPABILITIES, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0, + [](IpParameterObject * io, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t + { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + pushWord(0x1, data); + return 1; + }), + new DataProperty(PID_FRIENDLY_NAME, true, PDT_UNSIGNED_CHAR, 30, ReadLv3 | WriteLv3) + }; + initializeProperties(sizeof(properties), properties); +} + +uint16_t* IpParameterObject::additionalIndivualAddresses(uint8_t& numAddresses) +{ +#ifdef KNX_TUNNELING + numAddresses = KNX_TUNNELING; +#else + numAddresses = 0; +#endif + return (uint16_t*) propertyData(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES); +} + +#endif diff --git a/components/knx/src/knx/ip_parameter_object.h b/components/knx/src/knx/ip_parameter_object.h new file mode 100644 index 0000000..68f317c --- /dev/null +++ b/components/knx/src/knx/ip_parameter_object.h @@ -0,0 +1,20 @@ +#pragma once + +#include "config.h" +#ifdef USE_IP +#include "interface_object.h" +#include "device_object.h" +#include "platform.h" + +#define KNXIP_MULTICAST_PORT 3671 + +class IpParameterObject : public InterfaceObject +{ + public: + IpParameterObject(DeviceObject& deviceObject, Platform& platform); + uint16_t* additionalIndivualAddresses(uint8_t& numAddresses); + private: + DeviceObject& _deviceObject; + Platform& _platform; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_ch.cpp b/components/knx/src/knx/knx_ip_ch.cpp new file mode 100644 index 0000000..aa473fb --- /dev/null +++ b/components/knx/src/knx/knx_ip_ch.cpp @@ -0,0 +1,48 @@ +#include "knx_ip_ch.h" +#ifdef USE_IP +KnxIpCH::KnxIpCH(uint8_t* data) : _data(data) +{} + +KnxIpCH::~KnxIpCH() +{} + +uint8_t KnxIpCH::length() const +{ + return *_data; +} + +void KnxIpCH::length(uint8_t value) +{ + *_data = value; +} + +void KnxIpCH::channelId(uint8_t value) +{ + _data[1] = value; +} + +uint8_t KnxIpCH::channelId() const +{ + return _data[1]; +} + +void KnxIpCH::sequenceCounter(uint8_t value) +{ + _data[2] = value; +} + +uint8_t KnxIpCH::sequenceCounter() const +{ + return _data[2]; +} + +void KnxIpCH::status(uint8_t value) +{ + _data[3] = value; +} + +uint8_t KnxIpCH::status() const +{ + return _data[3]; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_ch.h b/components/knx/src/knx/knx_ip_ch.h new file mode 100644 index 0000000..5300c76 --- /dev/null +++ b/components/knx/src/knx/knx_ip_ch.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include "config.h" + +#ifdef USE_IP + +#define LEN_CH 4 + +// Connection Header +class KnxIpCH +{ + public: + KnxIpCH(uint8_t* data); + virtual ~KnxIpCH(); + void channelId(uint8_t channelId); + uint8_t channelId() const; + void sequenceCounter(uint8_t sequenceCounter); + uint8_t sequenceCounter() const; + void status(uint8_t status); + uint8_t status() const; + void length(uint8_t value); + uint8_t length() const; + + protected: + uint8_t* _data = 0; +}; +#endif diff --git a/components/knx/src/knx/knx_ip_config_dib.cpp b/components/knx/src/knx/knx_ip_config_dib.cpp new file mode 100644 index 0000000..096d1a2 --- /dev/null +++ b/components/knx/src/knx/knx_ip_config_dib.cpp @@ -0,0 +1,95 @@ +#include "knx_ip_config_dib.h" + +#ifdef USE_IP +KnxIpConfigDIB::KnxIpConfigDIB(uint8_t* data, bool isCurrent) : KnxIpDIB(data) +{ + _isCurrent = isCurrent; +} + +uint32_t KnxIpConfigDIB::address() +{ + uint32_t addr = 0; + popInt(addr, _data + 2); + return addr; +} + +void KnxIpConfigDIB::address(uint32_t addr) +{ + pushInt(addr, _data + 2); +} + +uint32_t KnxIpConfigDIB::subnet() +{ + uint32_t addr = 0; + popInt(addr, _data + 6); + return addr; +} + +void KnxIpConfigDIB::subnet(uint32_t addr) +{ + pushInt(addr, _data + 6); +} + +uint32_t KnxIpConfigDIB::gateway() +{ + uint32_t addr = 0; + popInt(addr, _data + 10); + return addr; +} + +void KnxIpConfigDIB::gateway(uint32_t addr) +{ + pushInt(addr, _data + 10); +} + +uint32_t KnxIpConfigDIB::dhcp() +{ + if (!_isCurrent) + return 0; + + uint32_t addr = 0; + popInt(addr, _data + 14); + return addr; +} + +void KnxIpConfigDIB::dhcp(uint32_t addr) +{ + if (!_isCurrent) + return; + + pushInt(addr, _data + 14); +} + +uint8_t KnxIpConfigDIB::info1() +{ + if (_isCurrent) + return _data[14]; + else + return _data[18]; +} + +void KnxIpConfigDIB::info1(uint8_t addr) +{ + if (_isCurrent) + _data[14] = addr; + else + _data[18] = addr; +} + +uint8_t KnxIpConfigDIB::info2() +{ + if (_isCurrent) + return _data[15]; + else + return _data[19]; +} + +void KnxIpConfigDIB::info2(uint8_t addr) +{ + if (_isCurrent) + _data[15] = addr; + else + _data[19] = addr; +} + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_config_dib.h b/components/knx/src/knx/knx_ip_config_dib.h new file mode 100644 index 0000000..a67bbd1 --- /dev/null +++ b/components/knx/src/knx/knx_ip_config_dib.h @@ -0,0 +1,28 @@ +#pragma once +#include "knx_ip_dib.h" +#include "bits.h" + +#ifdef USE_IP +#define LEN_IP_CONFIG_DIB 16 +#define LEN_IP_CURRENT_CONFIG_DIB 20 + +class KnxIpConfigDIB : public KnxIpDIB +{ + public: + KnxIpConfigDIB(uint8_t* data, bool isCurrent = false); + uint32_t address(); + void address(uint32_t addr); + uint32_t subnet(); + void subnet(uint32_t addr); + uint32_t gateway(); + void gateway(uint32_t addr); + uint32_t dhcp(); + void dhcp(uint32_t addr); + uint8_t info1(); + void info1(uint8_t addr); + uint8_t info2(); + void info2(uint8_t addr); + private: + bool _isCurrent = false; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_config_request.cpp b/components/knx/src/knx/knx_ip_config_request.cpp new file mode 100644 index 0000000..f3b0bf1 --- /dev/null +++ b/components/knx/src/knx/knx_ip_config_request.cpp @@ -0,0 +1,17 @@ +#include "knx_ip_config_request.h" +#ifdef USE_IP +KnxIpConfigRequest::KnxIpConfigRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _frame(data + LEN_KNXIP_HEADER + LEN_CH, length - LEN_KNXIP_HEADER - LEN_CH), _ch(data + LEN_KNXIP_HEADER) +{ +} + +CemiFrame& KnxIpConfigRequest::frame() +{ + return _frame; +} + +KnxIpCH& KnxIpConfigRequest::connectionHeader() +{ + return _ch; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_config_request.h b/components/knx/src/knx/knx_ip_config_request.h new file mode 100644 index 0000000..3ffe35b --- /dev/null +++ b/components/knx/src/knx/knx_ip_config_request.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_ch.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpConfigRequest : public KnxIpFrame +{ + public: + KnxIpConfigRequest(uint8_t* data, uint16_t length); + CemiFrame& frame(); + KnxIpCH& connectionHeader(); + private: + CemiFrame _frame; + KnxIpCH _ch; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_connect_request.cpp b/components/knx/src/knx/knx_ip_connect_request.cpp new file mode 100644 index 0000000..322c681 --- /dev/null +++ b/components/knx/src/knx/knx_ip_connect_request.cpp @@ -0,0 +1,21 @@ +#include "knx_ip_connect_request.h" +#ifdef USE_IP +KnxIpConnectRequest::KnxIpConnectRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER), _hpaiData(data + LEN_KNXIP_HEADER + LEN_IPHPAI), _cri(data + LEN_KNXIP_HEADER + 2 * LEN_IPHPAI) +{ +} + + +IpHostProtocolAddressInformation& KnxIpConnectRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +IpHostProtocolAddressInformation& KnxIpConnectRequest::hpaiData() +{ + return _hpaiData; +} +KnxIpCRI& KnxIpConnectRequest::cri() +{ + return _cri; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_connect_request.h b/components/knx/src/knx/knx_ip_connect_request.h new file mode 100644 index 0000000..76e75fc --- /dev/null +++ b/components/knx/src/knx/knx_ip_connect_request.h @@ -0,0 +1,19 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_cri.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpConnectRequest : public KnxIpFrame +{ + public: + KnxIpConnectRequest(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpaiCtrl(); + IpHostProtocolAddressInformation& hpaiData(); + KnxIpCRI& cri(); + private: + IpHostProtocolAddressInformation _hpaiCtrl; + IpHostProtocolAddressInformation _hpaiData; + KnxIpCRI _cri; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_connect_response.cpp b/components/knx/src/knx/knx_ip_connect_response.cpp new file mode 100644 index 0000000..0d59293 --- /dev/null +++ b/components/knx/src/knx/knx_ip_connect_response.cpp @@ -0,0 +1,46 @@ +#include "knx_ip_connect_response.h" +#ifdef USE_IP + +KnxIpConnectResponse::KnxIpConnectResponse(IpParameterObject& parameters, uint16_t address, uint16_t port, uint8_t channel, uint8_t type) + : KnxIpFrame(LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/ + LEN_IPHPAI + ((type == 4) ? 4 : 2)), + _controlEndpoint(_data + LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/), + _crd(_data + LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/ + LEN_IPHPAI) +{ + serviceTypeIdentifier(ConnectResponse); + + _data[LEN_KNXIP_HEADER] = channel; + + _controlEndpoint.length(LEN_IPHPAI); + _controlEndpoint.code(IPV4_UDP); + _controlEndpoint.ipAddress(parameters.propertyValue(PID_CURRENT_IP_ADDRESS)); + _controlEndpoint.ipPortNumber(KNXIP_MULTICAST_PORT); + + _crd.length((type == 4) ? 4 : 2); //TunnelConnectionResponse length = 4; ConfigConnectionResponse length = 2; + _crd.type(type); + if(type == 4) // only fill address when it is a TunnelConnectionResponse + _crd.address(address); +} + +KnxIpConnectResponse::KnxIpConnectResponse(uint8_t channel, uint8_t errorCode) + : KnxIpFrame(LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/), + _controlEndpoint(nullptr), + _crd(nullptr) +{ + serviceTypeIdentifier(ConnectResponse); + + _data[LEN_KNXIP_HEADER] = channel; + _data[LEN_KNXIP_HEADER + 1] = errorCode; +} + + +IpHostProtocolAddressInformation& KnxIpConnectResponse::controlEndpoint() +{ + return _controlEndpoint; +} + +KnxIpCRD& KnxIpConnectResponse::crd() +{ + return _crd; +} + +#endif diff --git a/components/knx/src/knx/knx_ip_connect_response.h b/components/knx/src/knx/knx_ip_connect_response.h new file mode 100644 index 0000000..fffa33c --- /dev/null +++ b/components/knx/src/knx/knx_ip_connect_response.h @@ -0,0 +1,45 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_crd.h" +#include "ip_host_protocol_address_information.h" +#include "knx_ip_device_information_dib.h" +#include "knx_ip_supported_service_dib.h" +#include "ip_parameter_object.h" +#ifdef USE_IP + +enum KnxIpConnectionRequestErrorCodes +{ + E_NO_ERROR = 0, + + E_HOST_PROTOCOL_TYPE = 0x01, + E_VERSION_NOT_SUPPORTED = 0x02, + E_SEQUENCE_NUMBER = 0x04, + + E_ERROR = 0x0F, + + E_CONNECTION_ID = 0x21, + E_CONNECTION_TYPE = 0x22, + E_CONNECTION_OPTION = 0x23, + E_NO_MORE_CONNECTIONS = 0x24, + E_DATA_CONNECTION = 0x26, + E_KNX_CONNECTION = 0x27, + E_AUTHORISATION_ERROR = 0x28, + E_TUNNELING_LAYER = 0x29, + E_NO_TUNNELLING_ADDRESS = 0x2D, + E_CONNECTION_IN_USE = 0x2E +}; + +class KnxIpConnectResponse : public KnxIpFrame +{ + public: + KnxIpConnectResponse(IpParameterObject& parameters, uint16_t address, uint16_t port, uint8_t channel, uint8_t type); + KnxIpConnectResponse(uint8_t channel, uint8_t errorCode); + IpHostProtocolAddressInformation& controlEndpoint(); + KnxIpCRD& crd(); + private: + IpHostProtocolAddressInformation _controlEndpoint; + KnxIpCRD _crd; +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_crd.cpp b/components/knx/src/knx/knx_ip_crd.cpp new file mode 100644 index 0000000..b9f96b0 --- /dev/null +++ b/components/knx/src/knx/knx_ip_crd.cpp @@ -0,0 +1,41 @@ +#include "knx_ip_crd.h" +#ifdef USE_IP +KnxIpCRD::KnxIpCRD(uint8_t* data) : _data(data) +{} + +KnxIpCRD::~KnxIpCRD() +{} + +uint8_t KnxIpCRD::length() const +{ + return *_data; +} + +void KnxIpCRD::length(uint8_t value) +{ + *_data = value; +} + +uint8_t KnxIpCRD::type() const +{ + return _data[1]; +} + +void KnxIpCRD::type(uint8_t value) +{ + _data[1] = value; +} + +uint16_t KnxIpCRD::address() const +{ + uint16_t addr = _data[3]; + addr |= _data[2] << 8; + return addr; +} + +void KnxIpCRD::address(uint16_t value) +{ + _data[2] = value >> 8; + _data[3] = value & 0xFF; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_crd.h b/components/knx/src/knx/knx_ip_crd.h new file mode 100644 index 0000000..1f95e79 --- /dev/null +++ b/components/knx/src/knx/knx_ip_crd.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "config.h" + +#ifdef USE_IP + +class KnxIpCRD +{ + public: + KnxIpCRD(uint8_t* data); + virtual ~KnxIpCRD(); + void address(uint16_t addr); + uint16_t address() const; + void type(uint8_t addr); + uint8_t type() const; + uint8_t length() const; + void length(uint8_t value); + + protected: + uint8_t* _data = 0; +}; +#endif diff --git a/components/knx/src/knx/knx_ip_cri.cpp b/components/knx/src/knx/knx_ip_cri.cpp new file mode 100644 index 0000000..8bb2107 --- /dev/null +++ b/components/knx/src/knx/knx_ip_cri.cpp @@ -0,0 +1,38 @@ +#include "knx_ip_cri.h" +#ifdef USE_IP +KnxIpCRI::KnxIpCRI(uint8_t* data) : _data(data) +{} + +KnxIpCRI::~KnxIpCRI() +{} + +uint8_t KnxIpCRI::length() const +{ + return *_data; +} + +void KnxIpCRI::length(uint8_t value) +{ + *_data = value; +} + +ConnectionType KnxIpCRI::type() const +{ + return (ConnectionType)_data[1]; +} + +void KnxIpCRI::type(ConnectionType value) +{ + _data[1] = value; +} + +uint8_t KnxIpCRI::layer() const +{ + return _data[2]; +} + +void KnxIpCRI::layer(uint8_t value) +{ + _data[2] = value; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_cri.h b/components/knx/src/knx/knx_ip_cri.h new file mode 100644 index 0000000..c8e2fad --- /dev/null +++ b/components/knx/src/knx/knx_ip_cri.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include "config.h" + +#ifdef USE_IP + +#define LEN_CRI 4 + +//TODO vervollständigen +enum ConnectionType : uint8_t +{ + DEVICE_MGMT_CONNECTION = 3, + TUNNEL_CONNECTION = 4, + REMLOG_CONNECTION = 6, + REMCONF_CONNECTION = 7, + OBJSVR_CONNECTION = 8 +}; + +// Connection Request Information +class KnxIpCRI +{ + public: + KnxIpCRI(uint8_t* data); + virtual ~KnxIpCRI(); + ConnectionType type() const; + void type(ConnectionType value); + void layer(uint8_t layer); + uint8_t layer() const; + uint8_t length() const; + void length(uint8_t value); + + protected: + uint8_t* _data = 0; +}; +#endif diff --git a/components/knx/src/knx/knx_ip_description_request.cpp b/components/knx/src/knx/knx_ip_description_request.cpp new file mode 100644 index 0000000..2e463f8 --- /dev/null +++ b/components/knx/src/knx/knx_ip_description_request.cpp @@ -0,0 +1,13 @@ +#include "knx_ip_description_request.h" +#ifdef USE_IP +KnxIpDescriptionRequest::KnxIpDescriptionRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER) +{ +} + + +IpHostProtocolAddressInformation& KnxIpDescriptionRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_description_request.h b/components/knx/src/knx/knx_ip_description_request.h new file mode 100644 index 0000000..f3e0e8d --- /dev/null +++ b/components/knx/src/knx/knx_ip_description_request.h @@ -0,0 +1,15 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_cri.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpDescriptionRequest : public KnxIpFrame +{ + public: + KnxIpDescriptionRequest(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpaiCtrl(); + private: + IpHostProtocolAddressInformation _hpaiCtrl; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_description_response.cpp b/components/knx/src/knx/knx_ip_description_response.cpp new file mode 100644 index 0000000..25c3dbd --- /dev/null +++ b/components/knx/src/knx/knx_ip_description_response.cpp @@ -0,0 +1,72 @@ +#include "knx_ip_description_response.h" +#ifdef USE_IP + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #endif +#else + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) + #endif +#endif + +KnxIpDescriptionResponse::KnxIpDescriptionResponse(IpParameterObject& parameters, DeviceObject& deviceObject) + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_DEVICE_INFORMATION_DIB + LEN_SERVICE_DIB), + _deviceInfo(_data + LEN_KNXIP_HEADER), + _supportedServices(_data + LEN_KNXIP_HEADER + LEN_DEVICE_INFORMATION_DIB) +{ + serviceTypeIdentifier(DescriptionResponse); + + _deviceInfo.length(LEN_DEVICE_INFORMATION_DIB); + _deviceInfo.code(DEVICE_INFO); +#if MASK_VERSION == 0x57B0 + _deviceInfo.medium(0x20); //MediumType is IP (for IP-Only Devices) +#else + _deviceInfo.medium(0x02); //MediumType is TP +#endif + _deviceInfo.status(deviceObject.progMode()); + _deviceInfo.individualAddress(parameters.propertyValue(PID_KNX_INDIVIDUAL_ADDRESS)); + _deviceInfo.projectInstallationIdentifier(parameters.propertyValue(PID_PROJECT_INSTALLATION_ID)); + _deviceInfo.serialNumber(deviceObject.propertyData(PID_SERIAL_NUMBER)); + _deviceInfo.routingMulticastAddress(parameters.propertyValue(PID_ROUTING_MULTICAST_ADDRESS)); + //_deviceInfo.routingMulticastAddress(0); + + uint8_t mac_address[LEN_MAC_ADDRESS] = {0}; + Property* prop = parameters.property(PID_MAC_ADDRESS); + prop->read(mac_address); + _deviceInfo.macAddress(mac_address); + + uint8_t friendlyName[LEN_FRIENDLY_NAME] = {0}; + prop = parameters.property(PID_FRIENDLY_NAME); + prop->read(1, LEN_FRIENDLY_NAME, friendlyName); + _deviceInfo.friendlyName(friendlyName); + + _supportedServices.length(LEN_SERVICE_DIB); + _supportedServices.code(SUPP_SVC_FAMILIES); + _supportedServices.serviceVersion(Core, 1); + _supportedServices.serviceVersion(DeviceManagement, 1); +#ifdef KNX_TUNNELING + _supportedServices.serviceVersion(Tunnelling, 1); +#endif +#if MASK_VERSION == 0x091A + _supportedServices.serviceVersion(Routing, 1); +#endif +} + +KnxIpDeviceInformationDIB& KnxIpDescriptionResponse::deviceInfo() +{ + return _deviceInfo; +} + + +KnxIpSupportedServiceDIB& KnxIpDescriptionResponse::supportedServices() +{ + return _supportedServices; +} +#endif diff --git a/components/knx/src/knx/knx_ip_description_response.h b/components/knx/src/knx/knx_ip_description_response.h new file mode 100644 index 0000000..efeaafe --- /dev/null +++ b/components/knx/src/knx/knx_ip_description_response.h @@ -0,0 +1,21 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#include "knx_ip_device_information_dib.h" +#include "knx_ip_supported_service_dib.h" +#include "ip_parameter_object.h" +#ifdef USE_IP + +class KnxIpDescriptionResponse : public KnxIpFrame +{ + public: + KnxIpDescriptionResponse(IpParameterObject& parameters, DeviceObject& deviceObj); + KnxIpDeviceInformationDIB& deviceInfo(); + KnxIpSupportedServiceDIB& supportedServices(); + private: + KnxIpDeviceInformationDIB _deviceInfo; + KnxIpSupportedServiceDIB _supportedServices; +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_device_information_dib.cpp b/components/knx/src/knx/knx_ip_device_information_dib.cpp new file mode 100644 index 0000000..a13ad96 --- /dev/null +++ b/components/knx/src/knx/knx_ip_device_information_dib.cpp @@ -0,0 +1,102 @@ +#include "knx_ip_device_information_dib.h" +#include "bits.h" + +#ifdef USE_IP +KnxIpDeviceInformationDIB::KnxIpDeviceInformationDIB(uint8_t* data) : KnxIpDIB(data) +{} + +uint8_t KnxIpDeviceInformationDIB::medium() const +{ + return _data[2]; +} + + +void KnxIpDeviceInformationDIB::medium(uint8_t value) +{ + _data[2] = value; +} + + +uint8_t KnxIpDeviceInformationDIB::status() const +{ + return _data[3]; +} + + +void KnxIpDeviceInformationDIB::status(uint8_t value) +{ + _data[3] = value; +} + + +uint16_t KnxIpDeviceInformationDIB::individualAddress() const +{ + return getWord(_data + 4); +} + + +void KnxIpDeviceInformationDIB::individualAddress(uint16_t value) +{ + pushWord(value, _data + 4); +} + + +uint16_t KnxIpDeviceInformationDIB::projectInstallationIdentifier() const +{ + return getWord(_data + 6); +} + + +void KnxIpDeviceInformationDIB::projectInstallationIdentifier(uint16_t value) +{ + pushWord(value, _data + 6); +} + + +const uint8_t* KnxIpDeviceInformationDIB::serialNumber() const +{ + return _data + 8; +} + + +void KnxIpDeviceInformationDIB::serialNumber(const uint8_t* value) +{ + pushByteArray(value, LEN_SERIAL_NUMBER, _data + 8); +} + + +uint32_t KnxIpDeviceInformationDIB::routingMulticastAddress() const +{ + return getInt(_data + 14); +} + + +void KnxIpDeviceInformationDIB::routingMulticastAddress(uint32_t value) +{ + pushInt(value, _data + 14); +} + + +const uint8_t* KnxIpDeviceInformationDIB::macAddress() const +{ + return _data + 18; +} + + +void KnxIpDeviceInformationDIB::macAddress(const uint8_t* value) +{ + pushByteArray(value, LEN_MAC_ADDRESS, _data + 18); +} + + +const uint8_t* KnxIpDeviceInformationDIB::friendlyName() const +{ + return _data + 24; +} + + +void KnxIpDeviceInformationDIB::friendlyName(const uint8_t* value) +{ + pushByteArray(value, LEN_FRIENDLY_NAME, _data + 24); +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_device_information_dib.h b/components/knx/src/knx/knx_ip_device_information_dib.h new file mode 100644 index 0000000..cc8de4b --- /dev/null +++ b/components/knx/src/knx/knx_ip_device_information_dib.h @@ -0,0 +1,32 @@ +#pragma once +#include "knx_ip_dib.h" + +#ifdef USE_IP +#define LEN_DEVICE_INFORMATION_DIB 54 +#define LEN_SERIAL_NUMBER 6 +#define LEN_MAC_ADDRESS 6 +#define LEN_FRIENDLY_NAME 30 + +class KnxIpDeviceInformationDIB : public KnxIpDIB +{ + public: + KnxIpDeviceInformationDIB(uint8_t* data); + uint8_t medium() const; + void medium(uint8_t value); + uint8_t status() const; + void status(uint8_t value); + uint16_t individualAddress() const; + void individualAddress(uint16_t value); + uint16_t projectInstallationIdentifier() const; + void projectInstallationIdentifier(uint16_t value); + const uint8_t* serialNumber() const; + void serialNumber(const uint8_t* value); + uint32_t routingMulticastAddress() const; + void routingMulticastAddress(uint32_t value); + const uint8_t* macAddress() const; + void macAddress(const uint8_t* value); + const uint8_t* friendlyName() const; + void friendlyName(const uint8_t* value); +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_dib.cpp b/components/knx/src/knx/knx_ip_dib.cpp new file mode 100644 index 0000000..f2b4a2d --- /dev/null +++ b/components/knx/src/knx/knx_ip_dib.cpp @@ -0,0 +1,28 @@ +#include "knx_ip_dib.h" +#ifdef USE_IP +KnxIpDIB::KnxIpDIB(uint8_t* data) : _data(data) +{} + +KnxIpDIB::~KnxIpDIB() +{} + +uint8_t KnxIpDIB::length() const +{ + return *_data; +} + +void KnxIpDIB::length(uint8_t value) +{ + *_data = value; +} + +DescriptionTypeCode KnxIpDIB::code() const +{ + return (DescriptionTypeCode)_data[1]; +} + +void KnxIpDIB::code(DescriptionTypeCode value) +{ + _data[1] = value; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_dib.h b/components/knx/src/knx/knx_ip_dib.h new file mode 100644 index 0000000..190fe09 --- /dev/null +++ b/components/knx/src/knx/knx_ip_dib.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include "config.h" + +#ifdef USE_IP + +enum DescriptionTypeCode : uint8_t +{ + DEVICE_INFO = 0x01, + SUPP_SVC_FAMILIES = 0x02, + IP_CONFIG = 0x03, + IP_CUR_CONFIG = 0x04, + KNX_ADDRESSES = 0x05, + MANUFACTURER_DATA = 0x06, + TUNNELING_INFO = 0x07, + EXTENDED_DEVICE_INFO = 0x08, + MFR_DATA = 0xFE +}; + +class KnxIpDIB +{ + public: + KnxIpDIB(uint8_t* data); + virtual ~KnxIpDIB(); + DescriptionTypeCode code() const; + void code(DescriptionTypeCode value); + uint8_t length() const; + void length(uint8_t value); + + protected: + uint8_t* _data = 0; +}; +#endif diff --git a/components/knx/src/knx/knx_ip_disconnect_request.cpp b/components/knx/src/knx/knx_ip_disconnect_request.cpp new file mode 100644 index 0000000..f5f041f --- /dev/null +++ b/components/knx/src/knx/knx_ip_disconnect_request.cpp @@ -0,0 +1,26 @@ +#include "knx_ip_disconnect_request.h" +#ifdef USE_IP +KnxIpDisconnectRequest::KnxIpDisconnectRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER + 1 /*ChannelId*/ + 1 /*Reserved*/) +{ +} + +KnxIpDisconnectRequest::KnxIpDisconnectRequest() + : KnxIpFrame(1 /*ChannelId*/ + 1 /*Reserved*/ + LEN_KNXIP_HEADER + LEN_IPHPAI), _hpaiCtrl(_data + 1 /*ChannelId*/ + 1 /*Reserved*/ + LEN_KNXIP_HEADER) +{ + serviceTypeIdentifier(DisconnectRequest); +} + +IpHostProtocolAddressInformation& KnxIpDisconnectRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +uint8_t KnxIpDisconnectRequest::channelId() +{ + return _data[LEN_KNXIP_HEADER]; +} +void KnxIpDisconnectRequest::channelId(uint8_t channelId) +{ + _data[LEN_KNXIP_HEADER] = channelId; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_disconnect_request.h b/components/knx/src/knx/knx_ip_disconnect_request.h new file mode 100644 index 0000000..a90eccb --- /dev/null +++ b/components/knx/src/knx/knx_ip_disconnect_request.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpDisconnectRequest : public KnxIpFrame +{ + public: + KnxIpDisconnectRequest(uint8_t* data, uint16_t length); + KnxIpDisconnectRequest(); + IpHostProtocolAddressInformation& hpaiCtrl(); + uint8_t channelId(); + void channelId(uint8_t channelId); + private: + IpHostProtocolAddressInformation _hpaiCtrl; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_disconnect_response.cpp b/components/knx/src/knx/knx_ip_disconnect_response.cpp new file mode 100644 index 0000000..a82cae2 --- /dev/null +++ b/components/knx/src/knx/knx_ip_disconnect_response.cpp @@ -0,0 +1,12 @@ +#include "knx_ip_disconnect_response.h" +#ifdef USE_IP + +KnxIpDisconnectResponse::KnxIpDisconnectResponse(uint8_t channel, uint8_t status) + : KnxIpFrame(LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/) +{ + serviceTypeIdentifier(DisconnectResponse); + + _data[LEN_KNXIP_HEADER] = channel; + _data[LEN_KNXIP_HEADER + 1] = status; +} +#endif diff --git a/components/knx/src/knx/knx_ip_disconnect_response.h b/components/knx/src/knx/knx_ip_disconnect_response.h new file mode 100644 index 0000000..553f72c --- /dev/null +++ b/components/knx/src/knx/knx_ip_disconnect_response.h @@ -0,0 +1,13 @@ +#pragma once + +#include "knx_ip_frame.h" +#ifdef USE_IP + +class KnxIpDisconnectResponse : public KnxIpFrame +{ + public: + KnxIpDisconnectResponse(uint8_t channel, uint8_t status); + private: +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_extended_device_information_dib.cpp b/components/knx/src/knx/knx_ip_extended_device_information_dib.cpp new file mode 100644 index 0000000..574f9a2 --- /dev/null +++ b/components/knx/src/knx/knx_ip_extended_device_information_dib.cpp @@ -0,0 +1,42 @@ +#include "knx_ip_extended_device_information_dib.h" +#include "bits.h" + +#ifdef USE_IP +KnxIpExtendedDeviceInformationDIB::KnxIpExtendedDeviceInformationDIB(uint8_t* data) : KnxIpDIB(data) +{} + +uint8_t KnxIpExtendedDeviceInformationDIB::status() const +{ + return _data[2]; +} + + +void KnxIpExtendedDeviceInformationDIB::status(uint8_t value) +{ + _data[2] = value; +} + + +uint16_t KnxIpExtendedDeviceInformationDIB::localMaxApdu() const +{ + return getWord(_data + 4); +} + + +void KnxIpExtendedDeviceInformationDIB::localMaxApdu(uint16_t value) +{ + pushWord(value, _data + 4); +} + + +uint16_t KnxIpExtendedDeviceInformationDIB::deviceDescriptor() const +{ + return getWord(_data + 6); +} + + +void KnxIpExtendedDeviceInformationDIB::deviceDescriptor(uint16_t value) +{ + pushWord(value, _data + 6); +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_extended_device_information_dib.h b/components/knx/src/knx/knx_ip_extended_device_information_dib.h new file mode 100644 index 0000000..923c4d4 --- /dev/null +++ b/components/knx/src/knx/knx_ip_extended_device_information_dib.h @@ -0,0 +1,19 @@ +#pragma once +#include "knx_ip_dib.h" + +#ifdef USE_IP +#define LEN_EXTENDED_DEVICE_INFORMATION_DIB 8 + +class KnxIpExtendedDeviceInformationDIB : public KnxIpDIB +{ + public: + KnxIpExtendedDeviceInformationDIB(uint8_t* data); + uint8_t status() const; + void status(uint8_t value); + uint16_t localMaxApdu() const; + void localMaxApdu(uint16_t value); + uint16_t deviceDescriptor() const; + void deviceDescriptor(uint16_t value); +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_frame.cpp b/components/knx/src/knx/knx_ip_frame.cpp new file mode 100644 index 0000000..9cf1ce2 --- /dev/null +++ b/components/knx/src/knx/knx_ip_frame.cpp @@ -0,0 +1,81 @@ +#include "knx_ip_frame.h" + +#ifdef USE_IP + +#include +#include "bits.h" + +#define KNXIP_HEADER_LEN 0x6 +#define KNXIP_PROTOCOL_VERSION 0x10 + +KnxIpFrame::KnxIpFrame(uint8_t* data, + uint16_t length) +{ + _data = data; + _dataLength = length; +} + +uint8_t KnxIpFrame::headerLength() const +{ + return _data[0]; +} + +void KnxIpFrame::headerLength(uint8_t length) +{ + _data[0] = length; +} + +KnxIpVersion KnxIpFrame::protocolVersion() const +{ + return (KnxIpVersion)_data[1]; +} + +void KnxIpFrame::protocolVersion(KnxIpVersion version) +{ + _data[1] = (uint8_t)version; +} + +uint16_t KnxIpFrame::serviceTypeIdentifier() const +{ + return getWord(_data + 2); +} + +void KnxIpFrame::serviceTypeIdentifier(uint16_t identifier) +{ + pushWord(identifier, _data + 2); +} + +uint16_t KnxIpFrame::totalLength() const +{ + return getWord(_data + 4); +} + +void KnxIpFrame::totalLength(uint16_t length) +{ + pushWord(length, _data + 4); +} + +uint8_t* KnxIpFrame::data() +{ + return _data; +} + + +KnxIpFrame::~KnxIpFrame() +{ + if (_freeData) + delete[] _data; +} + + +KnxIpFrame::KnxIpFrame(uint16_t length) +{ + _data = new uint8_t[length]; + _dataLength = length; + _freeData = true; + memset(_data, 0, length); + headerLength(LEN_KNXIP_HEADER); + protocolVersion(KnxIp1_0); + totalLength(length); +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_frame.h b/components/knx/src/knx/knx_ip_frame.h new file mode 100644 index 0000000..2c10e0f --- /dev/null +++ b/components/knx/src/knx/knx_ip_frame.h @@ -0,0 +1,57 @@ +#pragma once + +#include "cemi_frame.h" +#include "config.h" +#ifdef USE_IP + +#define LEN_KNXIP_HEADER 0x6 + +enum KnxIpVersion +{ + KnxIp1_0 = 0x10 +}; + +enum KnxIpServiceType +{ + SearchRequest = 0x201, + SearchResponse = 0x202, + DescriptionRequest = 0x203, + DescriptionResponse = 0x204, + ConnectRequest = 0x205, + ConnectResponse = 0x206, + ConnectionStateRequest = 0x207, + ConnectionStateResponse = 0x208, + DisconnectRequest = 0x209, + DisconnectResponse = 0x20A, + SearchRequestExt = 0x20B, + SearchResponseExt = 0x20C, + DeviceConfigurationRequest = 0x310, + DeviceConfigurationAck = 0x311, + TunnelingRequest = 0x420, + TunnelingAck = 0x421, + RoutingIndication = 0x530, + RoutingLostMessage = 0x531, +}; + +class KnxIpFrame +{ + public: + KnxIpFrame(uint8_t* data, uint16_t length); + KnxIpFrame(uint16_t totalLength); + virtual ~KnxIpFrame(); + uint8_t headerLength() const; + void headerLength(uint8_t length); + KnxIpVersion protocolVersion() const; + void protocolVersion(KnxIpVersion version); + uint16_t serviceTypeIdentifier() const; + void serviceTypeIdentifier(uint16_t identifier); + uint16_t totalLength() const; + void totalLength(uint16_t length); + uint8_t* data(); + + protected: + bool _freeData = false; + uint8_t* _data = 0; + uint16_t _dataLength; +}; +#endif diff --git a/components/knx/src/knx/knx_ip_knx_addresses_dib.cpp b/components/knx/src/knx/knx_ip_knx_addresses_dib.cpp new file mode 100644 index 0000000..63b5f7b --- /dev/null +++ b/components/knx/src/knx/knx_ip_knx_addresses_dib.cpp @@ -0,0 +1,27 @@ +#include "knx_ip_knx_addresses_dib.h" + +#ifdef USE_IP +KnxIpKnxAddressesDIB::KnxIpKnxAddressesDIB(uint8_t* data) : KnxIpDIB(data) +{ + currentPos = data + 4; +} + +uint16_t KnxIpKnxAddressesDIB::individualAddress() +{ + uint16_t addr = 0; + popWord(addr, _data + 2); + return addr; +} + +void KnxIpKnxAddressesDIB::individualAddress(uint16_t addr) +{ + pushInt(addr, _data + 2); +} + +void KnxIpKnxAddressesDIB::additional(uint16_t addr) +{ + pushWord(addr, currentPos); + currentPos += 2; + length(currentPos - _data); +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_knx_addresses_dib.h b/components/knx/src/knx/knx_ip_knx_addresses_dib.h new file mode 100644 index 0000000..757dcb3 --- /dev/null +++ b/components/knx/src/knx/knx_ip_knx_addresses_dib.h @@ -0,0 +1,17 @@ +#pragma once +#include "knx_ip_dib.h" +#include "bits.h" + +#ifdef USE_IP + +class KnxIpKnxAddressesDIB : public KnxIpDIB +{ + public: + KnxIpKnxAddressesDIB(uint8_t* data); + uint16_t individualAddress(); + void individualAddress(uint16_t addr); + void additional(uint16_t addr); + private: + uint8_t* currentPos = 0; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_routing_indication.cpp b/components/knx/src/knx/knx_ip_routing_indication.cpp new file mode 100644 index 0000000..0504570 --- /dev/null +++ b/components/knx/src/knx/knx_ip_routing_indication.cpp @@ -0,0 +1,22 @@ +#include "knx_ip_routing_indication.h" +#include + +#ifdef USE_IP +CemiFrame& KnxIpRoutingIndication::frame() +{ + return _frame; +} + + +KnxIpRoutingIndication::KnxIpRoutingIndication(uint8_t* data, + uint16_t length) : KnxIpFrame(data, length), _frame(data + headerLength(), length - headerLength()) +{ +} + +KnxIpRoutingIndication::KnxIpRoutingIndication(CemiFrame frame) + : KnxIpFrame(frame.totalLenght() + LEN_KNXIP_HEADER), _frame(_data + headerLength(), frame.totalLenght()) +{ + serviceTypeIdentifier(RoutingIndication); + memcpy(_data + LEN_KNXIP_HEADER, frame.data(), frame.totalLenght()); +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_routing_indication.h b/components/knx/src/knx/knx_ip_routing_indication.h new file mode 100644 index 0000000..38cef41 --- /dev/null +++ b/components/knx/src/knx/knx_ip_routing_indication.h @@ -0,0 +1,16 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "cemi_frame.h" +#ifdef USE_IP + +class KnxIpRoutingIndication : public KnxIpFrame +{ + public: + KnxIpRoutingIndication(uint8_t* data, uint16_t length); + KnxIpRoutingIndication(CemiFrame frame); + CemiFrame& frame(); + private: + CemiFrame _frame; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_search_request.cpp b/components/knx/src/knx/knx_ip_search_request.cpp new file mode 100644 index 0000000..40d236a --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_request.cpp @@ -0,0 +1,13 @@ +#include "knx_ip_search_request.h" +#ifdef USE_IP +KnxIpSearchRequest::KnxIpSearchRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpai(data + LEN_KNXIP_HEADER) +{ +} + + +IpHostProtocolAddressInformation& KnxIpSearchRequest::hpai() +{ + return _hpai; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_search_request.h b/components/knx/src/knx/knx_ip_search_request.h new file mode 100644 index 0000000..eea049f --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_request.h @@ -0,0 +1,14 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpSearchRequest : public KnxIpFrame +{ + public: + KnxIpSearchRequest(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpai(); + private: + IpHostProtocolAddressInformation _hpai; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_search_request_extended.cpp b/components/knx/src/knx/knx_ip_search_request_extended.cpp new file mode 100644 index 0000000..19fc7e4 --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_request_extended.cpp @@ -0,0 +1,69 @@ +#include "knx_ip_search_request_extended.h" +#include "bits.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 +#ifdef USE_IP +KnxIpSearchRequestExtended::KnxIpSearchRequestExtended(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpai(data + LEN_KNXIP_HEADER) +{ + if (length == LEN_KNXIP_HEADER + LEN_IPHPAI) + return; //we dont have SRPs + + int currentPos = LEN_KNXIP_HEADER + LEN_IPHPAI; + + while (currentPos < length) + { + switch (data[currentPos + 1]) + { + case 0x01: + srpByProgMode = true; + break; + + case 0x02: + srpByMacAddr = true; + srpMacAddr = data + currentPos + 2; + break; + + case 0x03: + srpByService = true; + srpServiceFamilies = data + currentPos; + break; + + case 0x04: + srpRequestDIBs = true; + + for (int i = 0; i < data[currentPos] - 2; i++) + { + if (data[currentPos + i + 2] == 0) + continue; + + if (data[currentPos + i + 2] > REQUESTED_DIBS_MAX) + { + print("Requested DIBs too high "); + continue; + } + + requestedDIBs[data[currentPos + i + 2]] = true; + } + + break; + } + + currentPos += data[currentPos]; + }; +} + +IpHostProtocolAddressInformation& KnxIpSearchRequestExtended::hpai() +{ + return _hpai; +} + +bool KnxIpSearchRequestExtended::requestedDIB(uint8_t code) +{ + if (code > REQUESTED_DIBS_MAX) + return false; + + return requestedDIBs[code]; +} +#endif +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_search_request_extended.h b/components/knx/src/knx/knx_ip_search_request_extended.h new file mode 100644 index 0000000..91ca702 --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_request_extended.h @@ -0,0 +1,26 @@ +#pragma once + +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +#define REQUESTED_DIBS_MAX 9 +class KnxIpSearchRequestExtended : public KnxIpFrame +{ + public: + KnxIpSearchRequestExtended(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpai(); + bool requestedDIB(uint8_t code); + bool srpByProgMode = false; + bool srpByMacAddr = false; + bool srpByService = false; + bool srpRequestDIBs = false; + uint8_t* srpMacAddr = nullptr; + uint8_t* srpServiceFamilies = nullptr; + private: + IpHostProtocolAddressInformation _hpai; + bool requestedDIBs[REQUESTED_DIBS_MAX]; //for now only 1 to 8 +}; +#endif +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_search_response.cpp b/components/knx/src/knx/knx_ip_search_response.cpp new file mode 100644 index 0000000..2a76610 --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_response.cpp @@ -0,0 +1,84 @@ +#include "knx_ip_search_response.h" +#ifdef USE_IP + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #endif +#else + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) + #endif +#endif + +KnxIpSearchResponse::KnxIpSearchResponse(IpParameterObject& parameters, DeviceObject& deviceObject) + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_IPHPAI + LEN_DEVICE_INFORMATION_DIB + LEN_SERVICE_DIB), + _controlEndpoint(_data + LEN_KNXIP_HEADER), _deviceInfo(_data + LEN_KNXIP_HEADER + LEN_IPHPAI), + _supportedServices(_data + LEN_KNXIP_HEADER + LEN_IPHPAI + LEN_DEVICE_INFORMATION_DIB) +{ + serviceTypeIdentifier(SearchResponse); + + _controlEndpoint.length(LEN_IPHPAI); + _controlEndpoint.code(IPV4_UDP); + _controlEndpoint.ipAddress(parameters.propertyValue(PID_CURRENT_IP_ADDRESS)); + _controlEndpoint.ipPortNumber(KNXIP_MULTICAST_PORT); + + _deviceInfo.length(LEN_DEVICE_INFORMATION_DIB); + _deviceInfo.code(DEVICE_INFO); +#if MASK_VERSION == 0x57B0 + _deviceInfo.medium(0x20); //MediumType is IP (for IP-Only Devices) +#else + _deviceInfo.medium(0x02); //MediumType is TP +#endif + _deviceInfo.status(deviceObject.progMode()); + _deviceInfo.individualAddress(parameters.propertyValue(PID_KNX_INDIVIDUAL_ADDRESS)); + _deviceInfo.projectInstallationIdentifier(parameters.propertyValue(PID_PROJECT_INSTALLATION_ID)); + _deviceInfo.serialNumber(deviceObject.propertyData(PID_SERIAL_NUMBER)); + _deviceInfo.routingMulticastAddress(parameters.propertyValue(PID_ROUTING_MULTICAST_ADDRESS)); + //_deviceInfo.routingMulticastAddress(0); + + uint8_t mac_address[LEN_MAC_ADDRESS] = {0}; + Property* prop = parameters.property(PID_MAC_ADDRESS); + prop->read(mac_address); + _deviceInfo.macAddress(mac_address); + + uint8_t friendlyName[LEN_FRIENDLY_NAME] = {0}; + prop = parameters.property(PID_FRIENDLY_NAME); + prop->read(1, LEN_FRIENDLY_NAME, friendlyName); + _deviceInfo.friendlyName(friendlyName); + + _supportedServices.length(LEN_SERVICE_DIB); + _supportedServices.code(SUPP_SVC_FAMILIES); + _supportedServices.serviceVersion(Core, KNX_SERVICE_FAMILY_CORE); + _supportedServices.serviceVersion(DeviceManagement, KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT); +#ifdef KNX_TUNNELING + _supportedServices.serviceVersion(Tunnelling, KNX_SERVICE_FAMILY_TUNNELING); +#endif +#if MASK_VERSION == 0x091A + _supportedServices.serviceVersion(Routing, KNX_SERVICE_FAMILY_ROUTING); +#endif +} + + +IpHostProtocolAddressInformation& KnxIpSearchResponse::controlEndpoint() +{ + return _controlEndpoint; +} + + +KnxIpDeviceInformationDIB& KnxIpSearchResponse::deviceInfo() +{ + return _deviceInfo; +} + + +KnxIpSupportedServiceDIB& KnxIpSearchResponse::supportedServices() +{ + return _supportedServices; +} +#endif diff --git a/components/knx/src/knx/knx_ip_search_response.h b/components/knx/src/knx/knx_ip_search_response.h new file mode 100644 index 0000000..33bda87 --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_response.h @@ -0,0 +1,24 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#include "knx_ip_device_information_dib.h" +#include "knx_ip_supported_service_dib.h" +#include "ip_parameter_object.h" +#include "service_families.h" +#ifdef USE_IP + +class KnxIpSearchResponse : public KnxIpFrame +{ + public: + KnxIpSearchResponse(IpParameterObject& parameters, DeviceObject& deviceObj); + IpHostProtocolAddressInformation& controlEndpoint(); + KnxIpDeviceInformationDIB& deviceInfo(); + KnxIpSupportedServiceDIB& supportedServices(); + private: + IpHostProtocolAddressInformation _controlEndpoint; + KnxIpDeviceInformationDIB _deviceInfo; + KnxIpSupportedServiceDIB _supportedServices; +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_search_response_extended.cpp b/components/knx/src/knx/knx_ip_search_response_extended.cpp new file mode 100644 index 0000000..1bd2acd --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_response_extended.cpp @@ -0,0 +1,227 @@ +#include "knx_ip_search_response_extended.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 +#ifdef USE_IP + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #endif +#else + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) + #endif +#endif + +KnxIpSearchResponseExtended::KnxIpSearchResponseExtended(IpParameterObject& parameters, DeviceObject& deviceObject, int dibLength) + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_IPHPAI + dibLength), + _controlEndpoint(_data + LEN_KNXIP_HEADER) +{ + serviceTypeIdentifier(SearchResponseExt); + + _controlEndpoint.length(LEN_IPHPAI); + _controlEndpoint.code(IPV4_UDP); + _controlEndpoint.ipAddress(parameters.propertyValue(PID_CURRENT_IP_ADDRESS)); + _controlEndpoint.ipPortNumber(KNXIP_MULTICAST_PORT); + + currentPos = LEN_KNXIP_HEADER + LEN_IPHPAI; +} + +void KnxIpSearchResponseExtended::setDeviceInfo(IpParameterObject& parameters, DeviceObject& deviceObject) +{ + //setDeviceInfo"); + KnxIpDeviceInformationDIB _deviceInfo(_data + currentPos); + _deviceInfo.length(LEN_DEVICE_INFORMATION_DIB); + _deviceInfo.code(DEVICE_INFO); +#if MASK_VERSION == 0x57B0 + _deviceInfo.medium(0x20); //MediumType is IP (for IP-Only Devices) +#else + _deviceInfo.medium(0x02); //MediumType is TP +#endif + _deviceInfo.status(deviceObject.progMode()); + _deviceInfo.individualAddress(parameters.propertyValue(PID_KNX_INDIVIDUAL_ADDRESS)); + _deviceInfo.projectInstallationIdentifier(parameters.propertyValue(PID_PROJECT_INSTALLATION_ID)); + _deviceInfo.serialNumber(deviceObject.propertyData(PID_SERIAL_NUMBER)); + _deviceInfo.routingMulticastAddress(parameters.propertyValue(PID_ROUTING_MULTICAST_ADDRESS)); + //_deviceInfo.routingMulticastAddress(0); + + uint8_t mac_address[LEN_MAC_ADDRESS] = {0}; + Property* prop = parameters.property(PID_MAC_ADDRESS); + prop->read(mac_address); + _deviceInfo.macAddress(mac_address); + + uint8_t friendlyName[LEN_FRIENDLY_NAME] = {0}; + prop = parameters.property(PID_FRIENDLY_NAME); + prop->read(1, LEN_FRIENDLY_NAME, friendlyName); + _deviceInfo.friendlyName(friendlyName); + + currentPos += LEN_DEVICE_INFORMATION_DIB; +} + +void KnxIpSearchResponseExtended::setSupportedServices() +{ + //println("setSupportedServices"); + KnxIpSupportedServiceDIB _supportedServices(_data + currentPos); + _supportedServices.length(LEN_SERVICE_DIB); + _supportedServices.code(SUPP_SVC_FAMILIES); + _supportedServices.serviceVersion(Core, KNX_SERVICE_FAMILY_CORE); + _supportedServices.serviceVersion(DeviceManagement, KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT); +#ifdef KNX_TUNNELING + _supportedServices.serviceVersion(Tunnelling, KNX_SERVICE_FAMILY_TUNNELING); +#endif +#if MASK_VERSION == 0x091A + _supportedServices.serviceVersion(Routing, KNX_SERVICE_FAMILY_ROUTING); +#endif + currentPos += LEN_SERVICE_DIB; +} + +void KnxIpSearchResponseExtended::setIpConfig(IpParameterObject& parameters) +{ + //println("setIpConfig"); + KnxIpConfigDIB _ipConfig(_data + currentPos); + _ipConfig.length(LEN_IP_CONFIG_DIB); + _ipConfig.code(IP_CONFIG); + _ipConfig.address(parameters.propertyValue(PID_IP_ADDRESS)); + _ipConfig.subnet(parameters.propertyValue(PID_SUBNET_MASK)); + _ipConfig.gateway(parameters.propertyValue(PID_DEFAULT_GATEWAY)); + _ipConfig.info1(parameters.propertyValue(PID_IP_CAPABILITIES)); + _ipConfig.info2(parameters.propertyValue(PID_IP_ASSIGNMENT_METHOD)); + + currentPos += LEN_IP_CONFIG_DIB; +} + +void KnxIpSearchResponseExtended::setIpCurrentConfig(IpParameterObject& parameters) +{ + //println("setIpCurrentConfig"); + KnxIpConfigDIB _ipCurConfig(_data + currentPos, true); + _ipCurConfig.length(LEN_IP_CURRENT_CONFIG_DIB); + _ipCurConfig.code(IP_CUR_CONFIG); + _ipCurConfig.address(parameters.propertyValue(PID_CURRENT_IP_ADDRESS)); + _ipCurConfig.subnet(parameters.propertyValue(PID_CURRENT_SUBNET_MASK)); + _ipCurConfig.gateway(parameters.propertyValue(PID_CURRENT_DEFAULT_GATEWAY)); + _ipCurConfig.dhcp(0); + _ipCurConfig.info1(parameters.propertyValue(PID_CURRENT_IP_ASSIGNMENT_METHOD)); + _ipCurConfig.info2(0x00); //Reserved + + currentPos += LEN_IP_CURRENT_CONFIG_DIB; +} + +void KnxIpSearchResponseExtended::setKnxAddresses(IpParameterObject& parameters, DeviceObject& deviceObject) +{ + //println("setKnxAddresses"); + KnxIpKnxAddressesDIB _knxAddresses(_data + currentPos); + _knxAddresses.length(4); //minimum + _knxAddresses.code(KNX_ADDRESSES); + _knxAddresses.individualAddress(deviceObject.individualAddress()); + + uint16_t length = 0; + parameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + + const uint8_t* addresses = parameters.propertyData(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES); + + for (int i = 0; i < length; i++) + { + uint16_t additional = 0; + popWord(additional, addresses + i * 2); + _knxAddresses.additional(additional); + } + + currentPos += _knxAddresses.length(); +} + +void KnxIpSearchResponseExtended::setTunnelingInfo(IpParameterObject& parameters, DeviceObject& deviceObject, KnxIpTunnelConnection tunnels[]) +{ + //println("setTunnelingInfo"); + KnxIpTunnelingInfoDIB _tunnelInfo(_data + currentPos); + _tunnelInfo.length(4); //minlength + _tunnelInfo.code(TUNNELING_INFO); + _tunnelInfo.apduLength(254); //FIXME where to get from + + uint16_t length = 0; + parameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + + const uint8_t* addresses; + + if (length == KNX_TUNNELING) + { + addresses = parameters.propertyData(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES); + } + else + { + uint8_t addrbuffer[KNX_TUNNELING * 2]; + addresses = (uint8_t*)addrbuffer; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + addrbuffer[i * 2 + 1] = i + 1; + addrbuffer[i * 2] = deviceObject.individualAddress() / 0x0100; + } + } + + for (int i = 0; i < length; i++) + { + uint16_t additional = 0; + popWord(additional, addresses + i * 2); + uint16_t flags = 0; + + uint8_t doubleCounter = 0; + bool used = false; + + for (int i = 0; i < KNX_TUNNELING; i++) + { + if (tunnels[i].IndividualAddress == additional) + { + doubleCounter += 1; + + if (tunnels[i].ChannelId != 0) + used = true; + } + } + + if (doubleCounter > 1 && used) + flags |= 1 << 2; //Slot is not usable; double PA is already used + + if (used) + { + flags |= 1 << 2; //Slot is not usable; PA is already used + flags |= 1; //Slot is not free + } + + flags = ~flags; + + _tunnelInfo.tunnelingSlot(additional, flags); + } + + currentPos += _tunnelInfo.length(); +} + +void KnxIpSearchResponseExtended::setExtendedDeviceInfo() +{ + //println("setExtendedDeviceInfo"); + KnxIpExtendedDeviceInformationDIB _extended(_data + currentPos); + _extended.length(LEN_EXTENDED_DEVICE_INFORMATION_DIB); + _extended.code(EXTENDED_DEVICE_INFO); + _extended.status(0x01); //FIXME dont know encoding PID_MEDIUM_STATUS=51 RouterObject + _extended.localMaxApdu(254); //FIXME is this correct? + _extended.deviceDescriptor(MASK_VERSION); + + currentPos += LEN_EXTENDED_DEVICE_INFORMATION_DIB; +} + +IpHostProtocolAddressInformation& KnxIpSearchResponseExtended::controlEndpoint() +{ + return _controlEndpoint; +} + + +uint8_t* KnxIpSearchResponseExtended::DIBs() +{ + return _data + LEN_KNXIP_HEADER + LEN_IPHPAI; +} +#endif +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_search_response_extended.h b/components/knx/src/knx/knx_ip_search_response_extended.h new file mode 100644 index 0000000..e0d8f8b --- /dev/null +++ b/components/knx/src/knx/knx_ip_search_response_extended.h @@ -0,0 +1,38 @@ +#pragma once + +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#include "knx_ip_device_information_dib.h" +#include "knx_ip_extended_device_information_dib.h" +#include "knx_ip_supported_service_dib.h" +#include "knx_ip_config_dib.h" +#include "knx_ip_knx_addresses_dib.h" +#include "knx_ip_tunneling_info_dib.h" +#include "ip_parameter_object.h" +#include "knx_ip_tunnel_connection.h" +#ifdef USE_IP + +class KnxIpSearchResponseExtended : public KnxIpFrame +{ + public: + KnxIpSearchResponseExtended(IpParameterObject& parameters, DeviceObject& deviceObj, int dibLength); + IpHostProtocolAddressInformation& controlEndpoint(); + void setDeviceInfo(IpParameterObject& parameters, DeviceObject& deviceObject); + void setSupportedServices(); + void setIpConfig(IpParameterObject& parameters); + void setIpCurrentConfig(IpParameterObject& parameters); + void setKnxAddresses(IpParameterObject& parameters, DeviceObject& deviceObject); + //setManuData + void setTunnelingInfo(IpParameterObject& parameters, DeviceObject& deviceObject, KnxIpTunnelConnection tunnels[]); + void setExtendedDeviceInfo(); + uint8_t* DIBs(); + private: + IpHostProtocolAddressInformation _controlEndpoint; + int currentPos = 0; +}; + +#endif +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_state_request.cpp b/components/knx/src/knx/knx_ip_state_request.cpp new file mode 100644 index 0000000..a1828d1 --- /dev/null +++ b/components/knx/src/knx/knx_ip_state_request.cpp @@ -0,0 +1,16 @@ +#include "knx_ip_state_request.h" +#ifdef USE_IP +KnxIpStateRequest::KnxIpStateRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER + 1 /*ChannelId*/ + 1 /*Reserved*/) +{ +} + +IpHostProtocolAddressInformation& KnxIpStateRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +uint8_t KnxIpStateRequest::channelId() +{ + return _data[LEN_KNXIP_HEADER]; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_state_request.h b/components/knx/src/knx/knx_ip_state_request.h new file mode 100644 index 0000000..99e74d2 --- /dev/null +++ b/components/knx/src/knx/knx_ip_state_request.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_cri.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpStateRequest : public KnxIpFrame +{ + public: + KnxIpStateRequest(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpaiCtrl(); + uint8_t channelId(); + private: + IpHostProtocolAddressInformation _hpaiCtrl; + +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_state_response.cpp b/components/knx/src/knx/knx_ip_state_response.cpp new file mode 100644 index 0000000..8f7b286 --- /dev/null +++ b/components/knx/src/knx/knx_ip_state_response.cpp @@ -0,0 +1,26 @@ +#include "knx_ip_state_response.h" +#ifdef USE_IP + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #endif +#else + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) + #endif +#endif + +KnxIpStateResponse::KnxIpStateResponse(uint8_t channelId, uint8_t errorCode) + : KnxIpFrame(LEN_KNXIP_HEADER + 2) +{ + serviceTypeIdentifier(ConnectionStateResponse); + _data[LEN_KNXIP_HEADER] = channelId; + _data[LEN_KNXIP_HEADER + 1] = errorCode; +} +#endif diff --git a/components/knx/src/knx/knx_ip_state_response.h b/components/knx/src/knx/knx_ip_state_response.h new file mode 100644 index 0000000..d8829be --- /dev/null +++ b/components/knx/src/knx/knx_ip_state_response.h @@ -0,0 +1,13 @@ +#pragma once + +#include "knx_ip_frame.h" +#ifdef USE_IP + +class KnxIpStateResponse : public KnxIpFrame +{ + public: + KnxIpStateResponse(uint8_t channelId, uint8_t errorCode); + private: +}; + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_supported_service_dib.cpp b/components/knx/src/knx/knx_ip_supported_service_dib.cpp new file mode 100644 index 0000000..4647ca8 --- /dev/null +++ b/components/knx/src/knx/knx_ip_supported_service_dib.cpp @@ -0,0 +1,44 @@ +#include "knx_ip_supported_service_dib.h" + +#ifdef USE_IP +KnxIpSupportedServiceDIB::KnxIpSupportedServiceDIB(uint8_t* data) : KnxIpDIB(data) +{} + + +uint8_t KnxIpSupportedServiceDIB::serviceVersion(ServiceFamily family) +{ + uint8_t* start = _data + 2; + uint8_t* end = _data + length(); + + for (uint8_t* it = start; it < end; it += 2) + { + if (*it == family) + return it[1]; + } + + return 0; +} + + +void KnxIpSupportedServiceDIB::serviceVersion(ServiceFamily family, uint8_t version) +{ + uint8_t* start = _data + 2; + uint8_t* end = _data + length(); + + for (uint8_t* it = start; it < end; it += 2) + { + if (*it == family) + { + it[1] = version; + break; + } + + if (*it == 0) + { + *it = family; + it[1] = version; + break; + } + } +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_supported_service_dib.h b/components/knx/src/knx/knx_ip_supported_service_dib.h new file mode 100644 index 0000000..c7b57c0 --- /dev/null +++ b/components/knx/src/knx/knx_ip_supported_service_dib.h @@ -0,0 +1,23 @@ +#pragma once +#include "knx_ip_dib.h" + +#ifdef USE_IP +enum ServiceFamily : uint8_t +{ + Core = 2, + DeviceManagement = 3, + Tunnelling = 4, + Routing = 5, + RemoteLogging = 6, + RemoteConfigDiag = 7, + ObjectServer = 8 +}; + +class KnxIpSupportedServiceDIB : public KnxIpDIB +{ + public: + KnxIpSupportedServiceDIB(uint8_t* data); + uint8_t serviceVersion(ServiceFamily family); + void serviceVersion(ServiceFamily family, uint8_t version); +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_tunnel_connection.cpp b/components/knx/src/knx/knx_ip_tunnel_connection.cpp new file mode 100644 index 0000000..06a04a8 --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunnel_connection.cpp @@ -0,0 +1,24 @@ +#include "knx_ip_tunnel_connection.h" + +KnxIpTunnelConnection::KnxIpTunnelConnection() +{ + +} + +void KnxIpTunnelConnection::Reset() +{ + print("Close Tunnel-Connection["); + print("?"); + print("], Channel: 0x"); + println(ChannelId, 16); + + ChannelId = 0; + IpAddress = 0; + PortData = 0; + PortCtrl = 0; + lastHeartbeat = 0; + SequenceCounter_S = 0; + SequenceCounter_R = 255; + IndividualAddress = 0; + IsConfig = false; +} diff --git a/components/knx/src/knx/knx_ip_tunnel_connection.h b/components/knx/src/knx/knx_ip_tunnel_connection.h new file mode 100644 index 0000000..6196e32 --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunnel_connection.h @@ -0,0 +1,24 @@ +#pragma once +#include "config.h" +#include "platform.h" +#include "bits.h" + +class KnxIpTunnelConnection +{ + public: + KnxIpTunnelConnection(); + uint8_t ChannelId = 0; + uint16_t IndividualAddress = 0; + uint32_t IpAddress = 0; + uint16_t PortData = 0; + uint16_t PortCtrl = 0; + uint8_t SequenceCounter_S = 0; + uint8_t SequenceCounter_R = 255; + unsigned long lastHeartbeat = 0; + bool IsConfig = false; + + void Reset(); + + private: + +}; \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_tunneling_ack.cpp b/components/knx/src/knx/knx_ip_tunneling_ack.cpp new file mode 100644 index 0000000..9e9981d --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunneling_ack.cpp @@ -0,0 +1,20 @@ +#include "knx_ip_tunneling_ack.h" +#include + +#ifdef USE_IP +KnxIpTunnelingAck::KnxIpTunnelingAck(uint8_t* data, + uint16_t length) : KnxIpFrame(data, length), _ch(_data + LEN_KNXIP_HEADER) +{ +} + +KnxIpTunnelingAck::KnxIpTunnelingAck() + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_CH), _ch(_data + LEN_KNXIP_HEADER) +{ + serviceTypeIdentifier(TunnelingAck); +} + +KnxIpCH& KnxIpTunnelingAck::connectionHeader() +{ + return _ch; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_tunneling_ack.h b/components/knx/src/knx/knx_ip_tunneling_ack.h new file mode 100644 index 0000000..da2fc6b --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunneling_ack.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "cemi_frame.h" +#include "knx_ip_ch.h" +#ifdef USE_IP + +class KnxIpTunnelingAck : public KnxIpFrame +{ + public: + KnxIpTunnelingAck(uint8_t* data, uint16_t length); + KnxIpTunnelingAck(); + KnxIpCH& connectionHeader(); + private: + KnxIpCH _ch; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_tunneling_info_dib.cpp b/components/knx/src/knx/knx_ip_tunneling_info_dib.cpp new file mode 100644 index 0000000..daafa60 --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunneling_info_dib.cpp @@ -0,0 +1,31 @@ +#include "knx_ip_tunneling_info_dib.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 + +#ifdef USE_IP +KnxIpTunnelingInfoDIB::KnxIpTunnelingInfoDIB(uint8_t* data) : KnxIpDIB(data) +{ + currentPos = data + 4; +} + +uint16_t KnxIpTunnelingInfoDIB::apduLength() +{ + uint16_t addr = 0; + popWord(addr, _data + 2); + return addr; +} + +void KnxIpTunnelingInfoDIB::apduLength(uint16_t addr) +{ + pushWord(addr, _data + 2); +} + +void KnxIpTunnelingInfoDIB::tunnelingSlot(uint16_t addr, uint16_t state) +{ + pushWord(addr, currentPos); + pushWord(state, currentPos + 2); + currentPos += 4; + length(currentPos - _data); +} +#endif +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_tunneling_info_dib.h b/components/knx/src/knx/knx_ip_tunneling_info_dib.h new file mode 100644 index 0000000..10aa55c --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunneling_info_dib.h @@ -0,0 +1,20 @@ +#pragma once +#include "knx_ip_dib.h" +#include "bits.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 + +#ifdef USE_IP + +class KnxIpTunnelingInfoDIB : public KnxIpDIB +{ + public: + KnxIpTunnelingInfoDIB(uint8_t* data); + uint16_t apduLength(); + void apduLength(uint16_t addr); + void tunnelingSlot(uint16_t addr, uint16_t state); + private: + uint8_t* currentPos = 0; +}; +#endif +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_tunneling_request.cpp b/components/knx/src/knx/knx_ip_tunneling_request.cpp new file mode 100644 index 0000000..3250703 --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunneling_request.cpp @@ -0,0 +1,26 @@ +#include "knx_ip_tunneling_request.h" +#include + +#ifdef USE_IP +KnxIpTunnelingRequest::KnxIpTunnelingRequest(uint8_t* data, + uint16_t length) : KnxIpFrame(data, length), _frame(data + LEN_CH + headerLength(), length - LEN_CH - headerLength()), _ch(_data + headerLength()) +{ +} + +KnxIpTunnelingRequest::KnxIpTunnelingRequest(CemiFrame frame) + : KnxIpFrame(frame.totalLenght() + LEN_CH + LEN_KNXIP_HEADER), _frame(_data + LEN_CH + LEN_KNXIP_HEADER, frame.totalLenght()), _ch(_data + LEN_KNXIP_HEADER) +{ + serviceTypeIdentifier(TunnelingRequest); + memcpy(_data + LEN_KNXIP_HEADER + LEN_CH, frame.data(), frame.totalLenght()); +} + +CemiFrame& KnxIpTunnelingRequest::frame() +{ + return _frame; +} + +KnxIpCH& KnxIpTunnelingRequest::connectionHeader() +{ + return _ch; +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_ip_tunneling_request.h b/components/knx/src/knx/knx_ip_tunneling_request.h new file mode 100644 index 0000000..5ac8099 --- /dev/null +++ b/components/knx/src/knx/knx_ip_tunneling_request.h @@ -0,0 +1,19 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "cemi_frame.h" +#include "knx_ip_ch.h" +#ifdef USE_IP + +class KnxIpTunnelingRequest : public KnxIpFrame +{ + public: + KnxIpTunnelingRequest(uint8_t* data, uint16_t length); + KnxIpTunnelingRequest(CemiFrame frame); + CemiFrame& frame(); + KnxIpCH& connectionHeader(); + private: + CemiFrame _frame; + KnxIpCH _ch; +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx/knx_types.h b/components/knx/src/knx/knx_types.h new file mode 100644 index 0000000..9e8afea --- /dev/null +++ b/components/knx/src/knx/knx_types.h @@ -0,0 +1,290 @@ +#pragma once + +enum FrameFormat +{ + ExtendedFrame = 0, + StandardFrame = 0x80 +}; + +enum Priority +{ + LowPriority = 0xC, //!< Normal priority of group communication. + NormalPriority = 0x4, //!< More important telegrams like central functions + UrgentPriority = 0x8, //!< Used for alarms. + SystemPriority = 0x0 //!< Mainly used by ETS for device programming. +}; + +enum AckType +{ + AckDontCare = 0, //!< We don't care about DataLinkLayer acknowledgement. + AckRequested = 0x2, //!< We want a DataLinkLayer acknowledgement. +}; + +enum TPAckType +{ + // see U_ACK_REQ defines in tpuart_data_link_layer.cpp + AckReqNack = 0x04, + AckReqBusy = 0x02, + AckReqAck = 0x01, + AckReqNone = 0x0, +}; + +enum AddressType +{ + IndividualAddress = 0, + GroupAddress = 0x80, +}; + +enum MessageCode +{ + // L_Data services + L_data_req = 0x11, + L_data_con = 0x2E, + L_data_ind = 0x29, + + // Data Properties + M_PropRead_req = 0xFC, + M_PropRead_con = 0xFB, + M_PropWrite_req = 0xF6, + M_PropWrite_con = 0xF5, + M_PropInfo_ind = 0xF7, + + // Function Properties + M_FuncPropCommand_req = 0xF8, + M_FuncPropCommand_con = 0xFA, + M_FuncPropStateRead_req = 0xF9, + M_FuncPropStateRead_con = 0xFA, // same as M_FuncPropStateRead_con (see 3/6/3 p.105) + + // Further cEMI servies + M_Reset_req = 0xF1, + M_Reset_ind = 0xF0, +}; + +enum cEmiErrorCode +{ + Unspecified_Error = 0x00, // unknown error (R/W) + Out_Of_Range = 0x01, // write value not allowed (general, if not error 2 or 3) (W) + Out_Of_Max_Range = 0x02, // write value to high (W) + Out_Of_Min_Range = 0x03, // write value to low (W) + Memory_Error = 0x04, // memory can not be written or only with fault(s) (W) + Read_Only = 0x05, // write access to a ‘read only’ or a write protected Property (W) + Illegal_Command = 0x06, // COMMAND not valid or not supported (W) + Void_DP = 0x07, // read or write access to an non existing Property (R/W) + Type_Conflict = 0x08, // write access with a wrong data type (Datapoint length) (W) + Prop_Index_Range_Error = 0x09, // read or write access to a non existing Property array index (R/W) + Value_temp_not_writeable = 0x0A, // The Property exists but can at this moment not be written with a new value (W) +}; + +// Unified return codes for KNX services and functions +// Note, that several older KNX services and functions do not use these return codes. +enum ReturnCodes +{ + // Generic positive return codes + Success = 0x00, // service, function or command executed sucessfully + SuccessWithCrc = 0x01, // positive message confirmation, CRC over original data + // Generic negative return codes + MemoryError = 0xF1, // memory cannot be accessed or only with fault(s) + InvalidCommand = 0xF2, // server does not support the requested command. ets: also non-existing or protected resource + ImpossibleCommand = 0xF3, // command cannot be executed because a dependency is not fulfilled + ExceedsMaxApduLength = 0xF4, // data will not fit into a frame supported by this server + DataOverflow = 0xF5, // attempt to write data beyond what is reserved for the addressed resource + OutOfMinRange = 0xF6, // write value below minimum supported value + OutOfMaxRange = 0xF7, // write value exceeds maximum supported value + DataVoid = 0xF8, // request contains invalid data + TemporarilyNotAvailable = 0xF9, // data access not possible at this time + AccessWriteOnly = 0xFA, // read access to write-only resource + AccessReadOnly = 0xFB, // write access to read-only resource + AccessDenied = 0xFC, // access to recource is not allowed because of authorization/security + AddressVoid = 0xFD, // resource is not present, address does not exist + DataTypeConflict = 0xFE, // write access with wrong datatype (datapoint length) + GenericError = 0xFF // service, function or command failed +}; + +enum Repetition +{ + NoRepitiion = 0, + WasRepeated = 0, + RepetitionAllowed = 0x20, + WasNotRepeated = 0x20, +}; + +enum SystemBroadcast +{ + SysBroadcast = 0, + Broadcast = 0x10, +}; + +enum Confirm +{ + ConfirmNoError = 0, + ConfirmError = 1, +}; + +enum HopCountType +{ + UnlimitedRouting, //!< NPDU::hopCount is set to 7. This means that the frame never expires. This could be a problem if your bus contains a circle. + NetworkLayerParameter //!< use NetworkLayer::hopCount as NPDU::hopCount +}; + +enum TpduType +{ + DataBroadcast, + DataGroup, + DataInduvidual, + DataConnected, + Connect, + Disconnect, + Ack, + Nack, +}; + +enum ApduType +{ + // Application Layer services on Multicast Communication Mode + GroupValueRead = 0x000, + GroupValueResponse = 0x040, + GroupValueWrite = 0x080, + + // Application Layer services on Broadcast Communication Mode + IndividualAddressWrite = 0x0c0, + IndividualAddressRead = 0x100, + IndividualAddressResponse = 0x140, + IndividualAddressSerialNumberRead = 0x3dc, + IndividualAddressSerialNumberResponse = 0x3dd, + IndividualAddressSerialNumberWrite = 0x3de, + + // Application Layer Services on System Broadcast communication mode + SystemNetworkParameterRead = 0x1c8, + SystemNetworkParameterResponse = 0x1c9, + SystemNetworkParameterWrite = 0x1ca, + // Open media specific Application Layer Services on System Broadcast communication mode + DomainAddressWrite = 0x3e0, + DomainAddressRead = 0x3e1, + DomainAddressResponse = 0x3e2, + DomainAddressSelectiveRead = 0x3e3, + DomainAddressSerialNumberRead = 0x3ec, + DomainAddressSerialNumberResponse = 0x3ed, + DomainAddressSerialNumberWrite = 0x3ee, + + // Application Layer Services on Point-to-point Connection-Oriented Communication Mode (mandatory) + // Application Layer Services on Point-to-point Connectionless Communication Mode (either optional or mandatory) + ADCRead = 0x0180, + ADCResponse = 0x01C0, + PropertyValueExtRead = 0x1CC, + PropertyValueExtResponse = 0x1CD, + PropertyValueExtWriteCon = 0x1CE, + PropertyValueExtWriteConResponse = 0x1CF, + PropertyValueExtWriteUnCon = 0x1D0, + PropertyExtDescriptionRead = 0x1D2, + PropertyExtDescriptionResponse = 0x1D3, + FunctionPropertyExtCommand = 0x1D4, + FunctionPropertyExtState = 0x1D5, + FunctionPropertyExtStateResponse = 0x1D6, + MemoryExtWrite = 0x1FB, + MemoryExtWriteResponse = 0x1FC, + MemoryExtRead = 0x1FD, + MemoryExtReadResponse = 0x1FE, + MemoryRead = 0x200, + MemoryResponse = 0x240, + MemoryWrite = 0x280, + UserMemoryRead = 0x2C0, + UserMemoryResponse = 0x2C1, + UserMemoryWrite = 0x2C2, + UserManufacturerInfoRead = 0x2C5, + UserManufacturerInfoResponse = 0x2C6, + FunctionPropertyCommand = 0x2C7, + FunctionPropertyState = 0x2C8, + FunctionPropertyStateResponse = 0x2C9, + DeviceDescriptorRead = 0x300, + DeviceDescriptorResponse = 0x340, + Restart = 0x380, + RestartMasterReset = 0x381, + RoutingTableOpen = 0x3C0, + RoutingTableRead = 0x3C1, + RoutingTableReadResponse = 0x3C2, + RoutingTableWrite = 0x3C3, + MemoryRouterWrite = 0x3CA, + MemoryRouterReadResponse = 0x3C9, + AuthorizeRequest = 0x3d1, + AuthorizeResponse = 0x3d2, + KeyWrite = 0x3d3, + KeyResponse = 0x3d4, + PropertyValueRead = 0x3d5, + PropertyValueResponse = 0x3d6, + PropertyValueWrite = 0x3d7, + PropertyDescriptionRead = 0x3d8, + PropertyDescriptionResponse = 0x3d9, + + // Secure Service + SecureService = 0x3F1 +}; + +enum DataSecurity +{ + None, + Auth, + AuthConf +}; + +struct SecurityControl +{ + bool toolAccess; + DataSecurity dataSecurity; +}; + +enum RestartType +{ + BasicRestart = 0x0, + MasterReset = 0x1 +}; + +enum EraseCode +{ + Void = 0x00, + ConfirmedRestart = 0x01, + FactoryReset = 0x02, + ResetIA = 0x03, + ResetAP = 0x04, + ResetParam = 0x05, + ResetLinks = 0x06, + FactoryResetWithoutIA = 0x07 +}; + +enum DptMedium +{ + // DPT_Medium (20.1004), range 0-255 + // All other values are reserved. + KNX_TP1 = 0x00, + KNX_PL110 = 0x01, + KNX_RF = 0x02, + KNX_IP = 0x05 +}; + +enum LCGRPCONFIG +{ + GROUP_6FFF = 0b00000011, + GROUP_7000 = 0b00001100, + GROUP_REPEAT = 0b00010000, + GROUP_6FFFUNLOCK = 0b00000001, + GROUP_6FFFLOCK = 0b00000010, + GROUP_6FFFROUTE = 0b00000011, + GROUP_7000UNLOCK = 0b00000100, + GROUP_7000LOCK = 0b00001000, + GROUP_7000ROUTE = 0b00001100 +}; + +enum LCCONFIG +{ + PHYS_FRAME = 0b00000011, + PHYS_FRAME_UNLOCK = 0b00000001, + PHYS_FRAME_LOCK = 0b00000010, + PHYS_FRAME_ROUT = 0b00000011, + PHYS_REPEAT = 0b00000100, + BROADCAST_LOCK = 0b00001000, + BROADCAST_REPEAT = 0b00010000, + GROUP_IACK_ROUT = 0b00100000, + PHYS_IACK = 0b11000000, + PHYS_IACK_NORMAL = 0b01000000, + PHYS_IACK_ALL = 0b10000000, + PHYS_IACK_NACK = 0b11000000 +}; \ No newline at end of file diff --git a/components/knx/src/knx/knx_value.cpp b/components/knx/src/knx/knx_value.cpp new file mode 100644 index 0000000..2e5b355 --- /dev/null +++ b/components/knx/src/knx/knx_value.cpp @@ -0,0 +1,626 @@ +#include "knx_value.h" + +#include +#include +#include + +KNXValue::KNXValue(bool value) +{ + _value.boolValue = value; + _type = BoolType; +} + +KNXValue::KNXValue(uint8_t value) +{ + _value.ucharValue = value; + _type = UCharType; +} + +KNXValue::KNXValue(uint16_t value) +{ + _value.ushortValue = value; + _type = UShortType; +} + +KNXValue::KNXValue(uint32_t value) +{ + _value.uintValue = value; + _type = UIntType; +} + +KNXValue::KNXValue(uint64_t value) +{ + _value.ulongValue = value; + _type = ULongType; +} + +KNXValue::KNXValue(int8_t value) +{ + _value.charValue = value; + _type = CharType; +} + +KNXValue::KNXValue(int16_t value) +{ + _value.shortValue = value; + _type = ShortType; +} + +KNXValue::KNXValue(int32_t value) +{ + _value.intValue = value; + _type = IntType; +} + +KNXValue::KNXValue(int64_t value) +{ + _value.longValue = value; + _type = LongType; +} + +KNXValue::KNXValue(double value) +{ + _value.doubleValue = value; + _type = DoubleType; +} + +KNXValue::KNXValue(const char* value) +{ + _value.stringValue = value; + _type = StringType; +} + +KNXValue::KNXValue(struct tm value) +{ + _value.timeValue = value; + _type = TimeType; +} + +KNXValue::operator bool() const +{ + return boolValue(); +} + +KNXValue::operator uint8_t() const +{ + return ucharValue(); +} + +KNXValue::operator uint16_t() const +{ + return ushortValue(); +} + +KNXValue::operator uint32_t() const +{ + return uintValue(); +} + +KNXValue::operator uint64_t() const +{ + return ulongValue(); +} + +KNXValue::operator int8_t() const +{ + return charValue(); +} + +KNXValue::operator int16_t() const +{ + return shortValue(); +} + +KNXValue::operator int32_t() const +{ + return intValue(); +} + +KNXValue::operator int64_t() const +{ + return longValue(); +} + +KNXValue::operator double() const +{ + return doubleValue(); +} + +KNXValue::operator const char* () const +{ + return stringValue(); +} + +KNXValue::operator struct tm() const +{ + return timeValue(); +} + +KNXValue& KNXValue::operator=(const bool value) +{ + _value.boolValue = value; + _type = BoolType; + return *this; +} + +KNXValue& KNXValue::operator=(const uint8_t value) +{ + _value.ucharValue = value; + _type = UCharType; + return *this; +} + +KNXValue& KNXValue::operator=(const uint16_t value) +{ + _value.ushortValue = value; + _type = UShortType; + return *this; +} + +KNXValue& KNXValue::operator=(const uint32_t value) +{ + _value.uintValue = value; + _type = UIntType; + return *this; +} + +KNXValue& KNXValue::operator=(const uint64_t value) +{ + _value.ulongValue = value; + _type = ULongType; + return *this; +} + +KNXValue& KNXValue::operator=(const int8_t value) +{ + _value.charValue = value; + _type = CharType; + return *this; +} + +KNXValue& KNXValue::operator=(const int16_t value) +{ + _value.shortValue = value; + _type = ShortType; + return *this; +} + +KNXValue& KNXValue::operator=(const int32_t value) +{ + _value.intValue = value; + _type = IntType; + return *this; +} + +KNXValue& KNXValue::operator=(const int64_t value) +{ + _value.longValue = value; + _type = LongType; + return *this; +} + +KNXValue& KNXValue::operator=(const double value) +{ + _value.doubleValue = value; + _type = DoubleType; + return *this; +} + +KNXValue& KNXValue::operator=(const char* value) +{ + _value.stringValue = value; + _type = StringType; + return *this; +} + +KNXValue& KNXValue::operator=(const struct tm value) +{ + _value.timeValue = value; + _type = TimeType; + return *this; +} + +bool KNXValue::boolValue() const +{ + switch (_type) + { + case BoolType: + return _value.boolValue; + + case UCharType: + case UShortType: + case UIntType: + case ULongType: + case CharType: + case ShortType: + case IntType: + case LongType: + case TimeType: + return longValue() != 0; + + case DoubleType: + return _value.doubleValue != 0; + + case StringType: + return strcmp(_value.stringValue, "true") == 0 || strcmp(_value.stringValue, "True") == 0 || longValue() != 0 || doubleValue() != 0; + } + + return 0; +} + +uint8_t KNXValue::ucharValue() const +{ + switch (_type) + { + case UCharType: + return _value.ucharValue; + + case BoolType: + case UShortType: + case UIntType: + case ULongType: + case TimeType: + return (uint8_t)ulongValue(); + + case CharType: + case ShortType: + case IntType: + case LongType: + case DoubleType: + case StringType: + return (uint8_t)longValue(); + } + + return 0; +} + +uint16_t KNXValue::ushortValue() const +{ + switch (_type) + { + case UShortType: + return _value.ushortValue; + + case BoolType: + case UCharType: + case UIntType: + case ULongType: + case TimeType: + return (uint16_t)ulongValue(); + + case CharType: + case ShortType: + case IntType: + case LongType: + case DoubleType: + case StringType: + return (uint16_t)longValue(); + } + + return 0; +} + +uint32_t KNXValue::uintValue() const +{ + switch (_type) + { + case UIntType: + return _value.uintValue; + + case BoolType: + case UCharType: + case UShortType: + case ULongType: + case TimeType: + return (uint32_t)ulongValue(); + + case CharType: + case ShortType: + case IntType: + case LongType: + case DoubleType: + case StringType: + return (uint32_t)longValue(); + } + + return 0; +} + +uint64_t KNXValue::ulongValue() const +{ + switch (_type) + { + case ULongType: + return _value.ulongValue; + + case BoolType: + return _value.boolValue ? 1 : 0; + + case UCharType: + return (uint64_t)_value.ucharValue; + + case UShortType: + return (uint64_t)_value.ushortValue; + + case UIntType: + return (uint64_t)_value.uintValue; + + case TimeType: + { + struct tm* timeptr = const_cast(&_value.timeValue); + return (uint64_t)mktime(timeptr); + } + + case CharType: + return (uint64_t)_value.charValue; + + case ShortType: + return (uint64_t)_value.shortValue; + + case IntType: + return (uint64_t)_value.intValue; + + case LongType: + return (uint64_t)_value.longValue; + + case DoubleType: + return (uint64_t)_value.doubleValue; + + case StringType: +#ifndef KNX_NO_STRTOx_CONVERSION + return (uint64_t)strtoul(_value.stringValue, NULL, 0); +#else + return 0; +#endif + } + + return 0; +} + +int8_t KNXValue::charValue() const +{ + switch (_type) + { + case CharType: + return _value.charValue; + + case BoolType: + case UCharType: + case UShortType: + case UIntType: + case ULongType: + case TimeType: + return (int8_t)ulongValue(); + + case ShortType: + case IntType: + case LongType: + case DoubleType: + case StringType: + return (int8_t)longValue(); + } + + return 0; +} + +int16_t KNXValue::shortValue() const +{ + switch (_type) + { + case ShortType: + return _value.shortValue; + + case BoolType: + case UCharType: + case UShortType: + case UIntType: + case ULongType: + case TimeType: + return (int16_t)ulongValue(); + + case CharType: + case IntType: + case LongType: + case DoubleType: + case StringType: + return (int16_t)longValue(); + } + + return 0; +} + +int32_t KNXValue::intValue() const +{ + switch (_type) + { + case IntType: + return _value.ulongValue; + + case BoolType: + case UCharType: + case UShortType: + case UIntType: + case ULongType: + case TimeType: + return (int32_t)ulongValue(); + + case CharType: + case ShortType: + case LongType: + case DoubleType: + case StringType: + return (int32_t)longValue(); + } + + return 0; +} + +int64_t KNXValue::longValue() const +{ + switch (_type) + { + case LongType: + return _value.longValue; + + case BoolType: + return _value.boolValue ? 1 : 0; + + case UCharType: + return (int64_t)_value.ucharValue; + + case UShortType: + return (int64_t)_value.ushortValue; + + case UIntType: + return (int64_t)_value.uintValue; + + case ULongType: + return (int64_t)_value.uintValue; + + case TimeType: + return (int64_t)ulongValue(); + + case CharType: + return (int64_t)_value.charValue; + + case ShortType: + return (int64_t)_value.shortValue; + + case IntType: + return (int64_t)_value.intValue; + + case DoubleType: + return (int64_t)_value.doubleValue; + + case StringType: +#ifndef KNX_NO_STRTOx_CONVERSION + return strtol(_value.stringValue, NULL, 0); +#else + return 0; +#endif + } + + return 0; +} + +double KNXValue::doubleValue() const +{ + switch (_type) + { + case DoubleType: + return _value.doubleValue; + + case BoolType: + return _value.boolValue ? 1 : 0; + + case UCharType: + return _value.ucharValue; + + case UShortType: + return _value.ushortValue; + + case UIntType: + return _value.uintValue; + + case ULongType: + return _value.uintValue; + + case TimeType: + return ulongValue(); + + case CharType: + return _value.charValue; + + case ShortType: + return _value.shortValue; + + case IntType: + return _value.intValue; + + case LongType: + return _value.longValue; + + case StringType: +#ifndef KNX_NO_STRTOx_CONVERSION + return strtod(_value.stringValue, NULL); +#else + return 0; +#endif + } + + return 0; +} + +const char* KNXValue::stringValue() const +{ + switch (_type) + { + case DoubleType: + case BoolType: + case UCharType: + case UShortType: + case UIntType: + case ULongType: + case TimeType: + case CharType: + case ShortType: + case IntType: + case LongType: + return ""; // we would have to manage the memory for the string otherwise. Maybe later. + + case StringType: + return _value.stringValue; + } + + return 0; +} + +struct tm KNXValue::timeValue() const +{ + switch (_type) + { + case TimeType: + return _value.timeValue; + + case BoolType: + case UCharType: + case UShortType: + case UIntType: + case ULongType: + case CharType: + case ShortType: + case IntType: + case LongType: + case DoubleType: + case StringType: + { + time_t timeVal = ulongValue(); + struct tm timeStruct; + gmtime_r(&timeVal, &timeStruct); + return timeStruct; + } + } + + struct tm tmp; + memset(&tmp, 0, sizeof(tmp)); + + return tmp; +} + +KNXValue::KNXValue(float value) +{ + _value.doubleValue = value; + _type = DoubleType; +} + +KNXValue& KNXValue::operator=(const float value) +{ + _value.doubleValue = value; + _type = DoubleType; + return *this; +} + +KNXValue::operator float() const +{ + return doubleValue(); +} \ No newline at end of file diff --git a/components/knx/src/knx/knx_value.h b/components/knx/src/knx/knx_value.h new file mode 100644 index 0000000..3443563 --- /dev/null +++ b/components/knx/src/knx/knx_value.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +class KNXValue +{ + public: + KNXValue(bool value); + KNXValue(uint8_t value); + KNXValue(uint16_t value); + KNXValue(uint32_t value); + KNXValue(uint64_t value); + KNXValue(int8_t value); + KNXValue(int16_t value); + KNXValue(int32_t value); + KNXValue(int64_t value); + KNXValue(double value); + KNXValue(const char* value); + KNXValue(struct tm value); + KNXValue(float value); + + operator bool() const; + operator uint8_t() const; + operator uint16_t() const; + operator uint32_t() const; + operator uint64_t() const; + operator int8_t() const; + operator int16_t() const; + operator int32_t() const; + operator int64_t() const; + operator double() const; + operator const char* () const; + operator struct tm() const; + operator float() const; + + KNXValue& operator=(const bool value); + KNXValue& operator=(const uint8_t value); + KNXValue& operator=(const uint16_t value); + KNXValue& operator=(const uint32_t value); + KNXValue& operator=(const uint64_t value); + KNXValue& operator=(const int8_t value); + KNXValue& operator=(const int16_t value); + KNXValue& operator=(const int32_t value); + KNXValue& operator=(const int64_t value); + KNXValue& operator=(const double value); + KNXValue& operator=(const char* value); + KNXValue& operator=(const struct tm value); + KNXValue& operator=(const float value); + + private: + bool boolValue() const; + uint8_t ucharValue() const; + uint16_t ushortValue() const; + uint32_t uintValue() const; + uint64_t ulongValue() const; + int8_t charValue() const; + int16_t shortValue() const; + int32_t intValue() const; + int64_t longValue() const; + double doubleValue() const; + const char* stringValue() const; + struct tm timeValue() const; + + + union Value + { + bool boolValue; + uint8_t ucharValue; + uint16_t ushortValue; + uint32_t uintValue; + uint64_t ulongValue; + int8_t charValue; + int16_t shortValue; + int32_t intValue; + int64_t longValue; + double doubleValue; + const char* stringValue; + struct tm timeValue; + }; + enum ValueType + { + BoolType, + UCharType, + UShortType, + UIntType, + ULongType, + CharType, + ShortType, + IntType, + LongType, + DoubleType, + StringType, + TimeType, + }; + + ValueType _type; + Value _value; +}; \ No newline at end of file diff --git a/components/knx/src/knx/memory.cpp b/components/knx/src/knx/memory.cpp new file mode 100644 index 0000000..2963f4e --- /dev/null +++ b/components/knx/src/knx/memory.cpp @@ -0,0 +1,562 @@ +#include "memory.h" + +#include + +#include "bits.h" + +Memory::Memory(Platform& platform, DeviceObject& deviceObject) + : _platform(platform), _deviceObject(deviceObject) +{} + +Memory::~Memory() +{} + +void Memory::readMemory() +{ + println("readMemory"); + + uint8_t* flashStart = _platform.getNonVolatileMemoryStart(); + size_t flashSize = _platform.getNonVolatileMemorySize(); + + if (flashStart == nullptr) + { + println("no user flash available;"); + return; + } + + printHex("RESTORED ", flashStart, _metadataSize); + + uint16_t metadataBlockSize = alignToPageSize(_metadataSize); + + _freeList = new MemoryBlock(flashStart + metadataBlockSize, flashSize - metadataBlockSize); + + uint16_t apiVersion = 0; + const uint8_t* buffer = popWord(apiVersion, flashStart); + + uint16_t manufacturerId = 0; + buffer = popWord(manufacturerId, buffer); + + uint8_t hardwareType[LEN_HARDWARE_TYPE] = {0}; + buffer = popByteArray(hardwareType, LEN_HARDWARE_TYPE, buffer); + + uint16_t version = 0; + buffer = popWord(version, buffer); + + VersionCheckResult versionCheck = FlashAllInvalid; + + // first check correct format of deviceObject-API + if (_deviceObject.apiVersion == apiVersion) + { + if (_versionCheckCallback != 0) + { + versionCheck = _versionCheckCallback(manufacturerId, hardwareType, version); + // callback should provide infomation about version check failure reasons + } + else if (_deviceObject.manufacturerId() == manufacturerId && + memcmp(_deviceObject.hardwareType(), hardwareType, LEN_HARDWARE_TYPE) == 0) + { + if (_deviceObject.version() == version) + { + versionCheck = FlashValid; + } + else + { + versionCheck = FlashTablesInvalid; + } + } + else + { + println("manufacturerId or hardwareType are different"); + print("expexted manufacturerId: "); + print(_deviceObject.manufacturerId(), HEX); + print(", stored manufacturerId: "); + println(manufacturerId, HEX); + print("expexted hardwareType: "); + printHex("", _deviceObject.hardwareType(), LEN_HARDWARE_TYPE); + print(", stored hardwareType: "); + printHex("", hardwareType, LEN_HARDWARE_TYPE); + println(""); + } + } + else + { + println("DataObject api changed, any data stored in flash is invalid."); + print("expexted DataObject api version: "); + print(_deviceObject.apiVersion, HEX); + print(", stored api version: "); + println(apiVersion, HEX); + } + + if (versionCheck == FlashAllInvalid) + { + println("ETS has to reprogram PA and application!"); + return; + } + + println("restoring data from flash..."); + print("saverestores "); + println(_saveCount); + + for (int i = 0; i < _saveCount; i++) + { + println(flashStart - buffer); + println("."); + buffer = _saveRestores[i]->restore(buffer); + } + + println("restored saveRestores"); + + if (versionCheck == FlashTablesInvalid) + { + println("TableObjects are referring to an older firmware version and are not loaded"); + return; + } + + print("tableObjs "); + println(_tableObjCount); + + for (int i = 0; i < _tableObjCount; i++) + { + println(flashStart - buffer); + println("."); + buffer = _tableObjects[i]->restore(buffer); + uint16_t memorySize = 0; + buffer = popWord(memorySize, buffer); + println(memorySize); + + if (memorySize == 0) + continue; + + // this works because TableObject saves a relative addr and restores it itself + addNewUsedBlock(_tableObjects[i]->_data, memorySize); + } + + println("restored Tableobjects"); +} + +void Memory::writeMemory() +{ + // first get the necessary size of the writeBuffer + uint16_t writeBufferSize = _metadataSize; + + for (int i = 0; i < _saveCount; i++) + writeBufferSize = MAX(writeBufferSize, _saveRestores[i]->saveSize()); + + for (int i = 0; i < _tableObjCount; i++) + writeBufferSize = MAX(writeBufferSize, _tableObjects[i]->saveSize() + 2 /*for memory pos*/); + + uint8_t buffer[writeBufferSize]; + uint32_t flashPos = 0; + uint8_t* bufferPos = buffer; + + bufferPos = pushWord(_deviceObject.apiVersion, bufferPos); + bufferPos = pushWord(_deviceObject.manufacturerId(), bufferPos); + bufferPos = pushByteArray(_deviceObject.hardwareType(), LEN_HARDWARE_TYPE, bufferPos); + bufferPos = pushWord(_deviceObject.version(), bufferPos); + + flashPos = _platform.writeNonVolatileMemory(flashPos, buffer, bufferPos - buffer); + + print("save saveRestores "); + println(_saveCount); + + for (int i = 0; i < _saveCount; i++) + { + bufferPos = _saveRestores[i]->save(buffer); + flashPos = _platform.writeNonVolatileMemory(flashPos, buffer, bufferPos - buffer); + } + + print("save tableobjs "); + println(_tableObjCount); + + for (int i = 0; i < _tableObjCount; i++) + { + bufferPos = _tableObjects[i]->save(buffer); + + //save to size of the memoryblock for tableobject too, so that we can rebuild the usedList and freeList + if (_tableObjects[i]->_data != nullptr) + { + MemoryBlock* block = findBlockInList(_usedList, _tableObjects[i]->_data); + + if (block == nullptr) + { + println("_data of TableObject not in _usedList"); + clearMemory(); + _platform.fatalError(); + } + + bufferPos = pushWord(block->size, bufferPos); + } + else + bufferPos = pushWord(0, bufferPos); + + flashPos = _platform.writeNonVolatileMemory(flashPos, buffer, bufferPos - buffer); + } + + _platform.commitNonVolatileMemory(); +} + +void Memory::saveMemory() +{ + _platform.commitNonVolatileMemory(); +} + +void Memory::clearMemory() +{ + _platform.writeNonVolatileMemory(0, 0xFF, _metadataSize); + _platform.commitNonVolatileMemory(); +} + +void Memory::addSaveRestore(SaveRestore* obj) +{ + if (_saveCount >= MAXSAVE - 1) + return; + + _saveRestores[_saveCount] = obj; + _saveCount += 1; + _metadataSize += obj->saveSize(); +} + +void Memory::addSaveRestore(TableObject* obj) +{ + if (_tableObjCount >= MAXTABLEOBJ) + return; + + _tableObjects[_tableObjCount] = obj; + _tableObjCount += 1; + _metadataSize += obj->saveSize(); + _metadataSize += 2; // for size +} + +uint8_t* Memory::allocMemory(size_t size) +{ + // always allocate aligned to pagesize + size = alignToPageSize(size); + + MemoryBlock* freeBlock = _freeList; + MemoryBlock* blockToUse = nullptr; + + // find the smallest possible block that is big enough + while (freeBlock) + { + if (freeBlock->size >= size) + { + if (blockToUse != nullptr && (blockToUse->size - size) > (freeBlock->size - size)) + blockToUse = freeBlock; + else if (blockToUse == nullptr) + blockToUse = freeBlock; + } + + freeBlock = freeBlock->next; + } + + if (!blockToUse) + { + println("No available non volatile memory!"); + _platform.fatalError(); + } + + if (blockToUse->size == size) + { + // use whole block + removeFromFreeList(blockToUse); + addToUsedList(blockToUse); + return blockToUse->address; + } + else + { + // split block + MemoryBlock* newBlock = new MemoryBlock(blockToUse->address, size); + addToUsedList(newBlock); + + blockToUse->address += size; + blockToUse->size -= size; + + return newBlock->address; + } +} + + +void Memory::freeMemory(uint8_t* ptr) +{ + MemoryBlock* block = _usedList; + MemoryBlock* found = nullptr; + + while (block) + { + if (block->address == ptr) + { + found = block; + break; + } + + block = block->next; + } + + if (!found) + { + println("freeMemory for not used pointer called"); + clearMemory(); + _platform.fatalError(); + } + + removeFromUsedList(block); + addToFreeList(block); + _saveTimeout = millis(); + if (_saveTimeout == 0) + _saveTimeout = 1; // prevent 0=disabled; no impact by minimal increased timeout +} + +void Memory::writeMemory(uint32_t relativeAddress, size_t size, uint8_t* data) +{ + if(_saveTimeout != 0) + { + _saveTimeout = millis(); + if (_saveTimeout == 0) + _saveTimeout = 1; // prevent 0=disabled; no impact by minimal increased timeout + } + _platform.writeNonVolatileMemory(relativeAddress, data, size); +} + +void Memory::readMemory(uint32_t relativeAddress, size_t size, uint8_t* data) +{ + _platform.readNonVolatileMemory(relativeAddress, data, size); +} + + +uint8_t* Memory::toAbsolute(uint32_t relativeAddress) +{ + return _platform.getNonVolatileMemoryStart() + (ptrdiff_t)relativeAddress; +} + + +uint32_t Memory::toRelative(uint8_t* absoluteAddress) +{ + return absoluteAddress - _platform.getNonVolatileMemoryStart(); +} + +MemoryBlock* Memory::removeFromList(MemoryBlock* head, MemoryBlock* item) +{ + if (head == item) + { + MemoryBlock* newHead = head->next; + head->next = nullptr; + return newHead; + } + + if (!head || !item) + { + println("invalid parameters of Memory::removeFromList"); + clearMemory(); + _platform.fatalError(); + } + + bool found = false; + MemoryBlock* block = head; + + while (block) + { + if (block->next == item) + { + found = true; + block->next = item->next; + break; + } + + block = block->next; + } + + if (!found) + { + println("tried to remove block from list not in it"); + clearMemory(); + _platform.fatalError(); + } + + item->next = nullptr; + return head; +} + +void Memory::removeFromFreeList(MemoryBlock* block) +{ + _freeList = removeFromList(_freeList, block); +} + + +void Memory::removeFromUsedList(MemoryBlock* block) +{ + _usedList = removeFromList(_usedList, block); +} + + +void Memory::addToUsedList(MemoryBlock* block) +{ + block->next = _usedList; + _usedList = block; +} + + +void Memory::addToFreeList(MemoryBlock* block) +{ + if (_freeList == nullptr) + { + _freeList = block; + return; + } + + // first insert free block in list + MemoryBlock* current = _freeList; + + while (current) + { + if (current->address <= block->address && (current->next == nullptr || block->address < current->next->address)) + { + //add after current + block->next = current->next; + current->next = block; + break; + } + else if (current->address > block->address) + { + //add before current + block->next = current; + + if (current == _freeList) + _freeList = block; + + // swap current and block for merge + MemoryBlock* tmp = current; + current = block; + block = tmp; + + break; + } + + current = current->next; + } + + // now check if we can merge the blocks + // first current an block + if ((current->address + current->size) == block->address) + { + current->size += block->size; + current->next = block->next; + delete block; + // check further if now current can be merged with current->next + block = current; + } + + // if block is the last one, we are done + if (block->next == nullptr) + return; + + // now check block and block->next + if ((block->address + block->size) == block->next->address) + { + block->size += block->next->size; + block->next = block->next->next; + delete block->next; + } +} + +uint16_t Memory::alignToPageSize(size_t size) +{ + size_t pageSize = 4; //_platform.flashPageSize(); // align to 32bit for now, as aligning to flash-page-size causes side effects in programming + // pagesize should be a multiply of two + return (size + pageSize - 1) & (-1 * pageSize); +} + +MemoryBlock* Memory::findBlockInList(MemoryBlock* head, uint8_t* address) +{ + while (head != nullptr) + { + if (head->address == address) + return head; + + head = head->next; + } + + return nullptr; +} + +void Memory::addNewUsedBlock(uint8_t* address, size_t size) +{ + MemoryBlock* smallerFreeBlock = _freeList; + + // find block in freeList where the new used block is contained in + while (smallerFreeBlock) + { + if (smallerFreeBlock->next == nullptr || + (smallerFreeBlock->next != nullptr && smallerFreeBlock->next->address > address)) + break; + + smallerFreeBlock = smallerFreeBlock->next; + } + + if (smallerFreeBlock == nullptr) + { + println("addNewUsedBlock: no smallerBlock found"); + clearMemory(); + _platform.fatalError(); + } + + if ((smallerFreeBlock->address + smallerFreeBlock->size) < (address + size)) + { + println("addNewUsedBlock: found block can't contain new block"); + clearMemory(); + _platform.fatalError(); + } + + if (smallerFreeBlock->address == address && smallerFreeBlock->size == size) + { + // we take thow whole block + removeFromFreeList(smallerFreeBlock); + addToUsedList(smallerFreeBlock); + return; + } + + if (smallerFreeBlock->address == address) + { + // we take a front part of the block + smallerFreeBlock->address += size; + smallerFreeBlock->size -= size; + } + else + { + // we take a middle or end part of the block + uint8_t* oldEndAddr = smallerFreeBlock->address + smallerFreeBlock->size; + smallerFreeBlock->size = (address - smallerFreeBlock->address); + + if (address + size < oldEndAddr) + { + // we take the middle part of the block, so we need a new free block for the end part + MemoryBlock* newFreeBlock = new MemoryBlock(); + newFreeBlock->next = smallerFreeBlock->next; + newFreeBlock->address = address + size; + newFreeBlock->size = oldEndAddr - newFreeBlock->address; + smallerFreeBlock->next = newFreeBlock; + } + } + + MemoryBlock* newUsedBlock = new MemoryBlock(address, size); + addToUsedList(newUsedBlock); +} + +void Memory::versionCheckCallback(VersionCheckCallback func) +{ + _versionCheckCallback = func; +} + +VersionCheckCallback Memory::versionCheckCallback() +{ + return _versionCheckCallback; +} + +void Memory::loop() +{ + if(_saveTimeout != 0 && millis() - _saveTimeout > 5000) + { + println("saveMemory timeout"); + _saveTimeout = 0; + writeMemory(); + } +} \ No newline at end of file diff --git a/components/knx/src/knx/memory.h b/components/knx/src/knx/memory.h new file mode 100644 index 0000000..0b0e983 --- /dev/null +++ b/components/knx/src/knx/memory.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include "save_restore.h" +#include "platform.h" +#include "device_object.h" +#include "table_object.h" + +#define MAXSAVE 5 +#define MAXTABLEOBJ 4 + +#ifndef KNX_FLASH_SIZE + #define KNX_FLASH_SIZE 1024 +#endif + +class MemoryBlock +{ + public: + MemoryBlock() {}; + MemoryBlock(uint8_t* address, size_t size) + : address(address), size(size) {} + uint8_t* address = nullptr; + size_t size = 0; + MemoryBlock* next = nullptr; +}; + +enum VersionCheckResult +{ + FlashAllInvalid = 0, //!< All flash content is not valid for this firmware, we delete it + FlashTablesInvalid = 1,//!< All table objects are invalid for this firmware, device object and saveRestores are OK + FlashValid = 2 //!< Flash content is valid and will be used +}; + +typedef VersionCheckResult (*VersionCheckCallback)(uint16_t manufacturerId, uint8_t* hardwareType, uint16_t version); + +class Memory +{ + friend class TableObject; + + public: + Memory(Platform& platform, DeviceObject& deviceObject); + virtual ~Memory(); + void readMemory(); + void writeMemory(); + void saveMemory(); + void clearMemory(); + void addSaveRestore(SaveRestore* obj); + void addSaveRestore(TableObject* obj); + void loop(); + + uint8_t* allocMemory(size_t size); + void freeMemory(uint8_t* ptr); + void writeMemory(uint32_t relativeAddress, size_t size, uint8_t* data); + void readMemory(uint32_t relativeAddress, size_t size, uint8_t* data); + uint8_t* toAbsolute(uint32_t relativeAddress); + uint32_t toRelative(uint8_t* absoluteAddress); + + void versionCheckCallback(VersionCheckCallback func); + VersionCheckCallback versionCheckCallback(); + + private: + void removeFromFreeList(MemoryBlock* block); + void addToUsedList(MemoryBlock* block); + void removeFromUsedList(MemoryBlock* block); + void addToFreeList(MemoryBlock* block); + uint16_t alignToPageSize(size_t size); + MemoryBlock* removeFromList(MemoryBlock* head, MemoryBlock* item); + MemoryBlock* findBlockInList(MemoryBlock* head, uint8_t* address); + void addNewUsedBlock(uint8_t* address, size_t size); + + void readEraseBlockToBuffer(uint32_t blockNum); + uint8_t* eraseBlockStart(uint32_t blockNum); + uint8_t* eraseBlockEnd(uint32_t blockNum); + void saveBufferdEraseBlock(); + + VersionCheckCallback _versionCheckCallback = 0; + Platform& _platform; + DeviceObject& _deviceObject; + SaveRestore* _saveRestores[MAXSAVE] = {0}; + TableObject* _tableObjects[MAXTABLEOBJ] = {0}; + uint8_t _saveCount = 0; + uint8_t _tableObjCount = 0; + MemoryBlock* _freeList = nullptr; + MemoryBlock* _usedList = nullptr; + uint16_t _metadataSize = 6 + LEN_HARDWARE_TYPE; // accounting for 3x pushWord and pushByteArray of length LEN_HARDWARE_TYPE + unsigned long _saveTimeout = 0; +}; diff --git a/components/knx/src/knx/network_layer.cpp b/components/knx/src/knx/network_layer.cpp new file mode 100644 index 0000000..1384751 --- /dev/null +++ b/components/knx/src/knx/network_layer.cpp @@ -0,0 +1,46 @@ +#include "network_layer.h" +#include "device_object.h" +#include "data_link_layer.h" +#include "tpdu.h" +#include "cemi_frame.h" +#include "bits.h" +#include "apdu.h" + +NetworkLayer::NetworkLayer(DeviceObject& deviceObj, TransportLayer& layer) : + _deviceObj(deviceObj), + _transportLayer(layer) +{ + _hopCount = _deviceObj.defaultHopCount(); +} + +uint8_t NetworkLayer::hopCount() const +{ + return _hopCount; +} + +bool NetworkLayer::isApciSystemBroadcast(APDU& apdu) +{ + switch (apdu.type()) + { + // Application Layer Services on System Broadcast communication mode + case SystemNetworkParameterRead: + case SystemNetworkParameterResponse: + case SystemNetworkParameterWrite: + + // Open media specific Application Layer Services on System Broadcast communication mode + case DomainAddressSerialNumberRead: + case DomainAddressSerialNumberResponse: + case DomainAddressSerialNumberWrite: + case DomainAddressRead: + case DomainAddressSelectiveRead: + case DomainAddressResponse: + case DomainAddressWrite: + return true; + + default: + return false; + } + + return false; +} + diff --git a/components/knx/src/knx/network_layer.h b/components/knx/src/knx/network_layer.h new file mode 100644 index 0000000..28be8d6 --- /dev/null +++ b/components/knx/src/knx/network_layer.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "knx_types.h" +#include "npdu.h" +#include "transport_layer.h" +#include "network_layer_entity.h" + +class DeviceObject; +class APDU; + +class NetworkLayer +{ + friend class NetworkLayerEntity; + + public: + NetworkLayer(DeviceObject& deviceObj, TransportLayer& layer); + + uint8_t hopCount() const; + bool isApciSystemBroadcast(APDU& apdu); + + // from transport layer + virtual void dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) = 0; + virtual void dataGroupRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) = 0; + virtual void dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) = 0; + virtual void dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) = 0; + + protected: + DeviceObject& _deviceObj; + TransportLayer& _transportLayer; + + // from entities + virtual void dataIndication(AckType ack, AddressType addType, uint16_t destination, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) = 0; + virtual void dataConfirm(AckType ack, AddressType addressType, uint16_t destination, FrameFormat format, Priority priority, + uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) = 0; + virtual void broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) = 0; + virtual void broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) = 0; + virtual void systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) = 0; + virtual void systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) = 0; + + private: + uint8_t _hopCount; // Network Layer Parameter hop_count for the device's own outgoing frames (default value from PID_ROUTING_COUNT) +}; diff --git a/components/knx/src/knx/network_layer_coupler.cpp b/components/knx/src/knx/network_layer_coupler.cpp new file mode 100644 index 0000000..6583e83 --- /dev/null +++ b/components/knx/src/knx/network_layer_coupler.cpp @@ -0,0 +1,656 @@ +#include "network_layer_coupler.h" +#include "data_link_layer.h" +#include "device_object.h" +#include "router_object.h" +#include "tpdu.h" +#include "cemi_frame.h" +#include "bits.h" + +NetworkLayerCoupler::NetworkLayerCoupler(DeviceObject& deviceObj, + TransportLayer& layer) : + NetworkLayer(deviceObj, layer), + _netLayerEntities { {*this, kPrimaryIfIndex}, {*this, kSecondaryIfIndex} } +{ + _currentAddress = deviceObj.individualAddress(); + evaluateCouplerType(); +} + +NetworkLayerEntity& NetworkLayerCoupler::getPrimaryInterface() +{ + return _netLayerEntities[0]; +} + +NetworkLayerEntity& NetworkLayerCoupler::getSecondaryInterface() +{ + return _netLayerEntities[1]; +} + +void NetworkLayerCoupler::rtObj(RouterObject& rtObj) +{ + _rtObjPrimary = &rtObj; + _rtObjSecondary = nullptr; +} + +void NetworkLayerCoupler::rtObjPrimary(RouterObject& rtObjPrimary) +{ + _rtObjPrimary = &rtObjPrimary; +} + +void NetworkLayerCoupler::rtObjSecondary(RouterObject& rtObjSecondary) +{ + _rtObjSecondary = &rtObjSecondary; +} + +void NetworkLayerCoupler::evaluateCouplerType() +{ + // Check coupler mode + if ((_deviceObj.individualAddress() & 0x00FF) == 0x00) + { + // Device is a router + // Check if line coupler or backbone coupler + if ((_deviceObj.individualAddress() & 0x0F00) == 0x0) + { + // Device is a backbone coupler -> individual address: x.0.0 + _couplerType = BackboneCoupler; + } + else + { + // Device is a line coupler -> individual address: x.y.0 + _couplerType = LineCoupler; + } + } + else + { + // Device is not a router, check if TP1 bridge or TP1 repeater + /* + if (PID_L2_COUPLER_TYPE.BIT0 == 0) + { + //then Device is TP1 Bridge + couplerType = TP1Bridge; + } + else + { + // Device is TP1 Repeater + couplerType = TP1Repeater; + } + */ + } +} + +bool NetworkLayerCoupler::isGroupAddressInFilterTable(uint16_t groupAddress) +{ + if (_rtObjSecondary == nullptr) + return (_rtObjPrimary != nullptr) ? _rtObjPrimary->isGroupAddressInFilterTable(groupAddress) : false; + else + { + return _rtObjSecondary->isGroupAddressInFilterTable(groupAddress); + } +} + +bool NetworkLayerCoupler::isRoutedGroupAddress(uint16_t groupAddress, uint8_t sourceInterfaceIndex) +{ + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + uint8_t lcgrpconfig = LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT; // default value from spec. in case prop is not availible. + Property* prop_lcgrpconfig; + Property* prop_lcconfig; + + if (sourceInterfaceIndex == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_MAIN_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + } + else // direction Sec -> Prim ( e.g. TP -> IP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_SUB_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + } + + if (prop_lcgrpconfig) + prop_lcgrpconfig->read(lcgrpconfig); + + if (prop_lcconfig) + prop_lcconfig->read(lcconfig); + + + if (groupAddress < 0x7000) // Main group 0-13 + { + // PID_SUB_LCGRPCONFIG Bit 0-1 + switch (lcgrpconfig & LCGRPCONFIG::GROUP_6FFF) + { + case LCGRPCONFIG::GROUP_6FFFLOCK: + //printHex("1drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + break; + + case LCGRPCONFIG::GROUP_6FFFROUTE: + if (isGroupAddressInFilterTable(groupAddress)) + ;//send + else + { + //printHex("2drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + } + + break; + + default: // LCGRPCONFIG::GROUP_6FFFUNLOCK + ;//send + } + } + else // Main group 14-31 + { + // PID_SUB_LCGRPCONFIG Bit 2-3 LCGRPCONFIG::GROUP_7000 + switch (lcgrpconfig & LCGRPCONFIG::GROUP_7000) + { + case LCGRPCONFIG::GROUP_7000LOCK: + //printHex("3drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + break; + + case LCGRPCONFIG::GROUP_7000ROUTE: + if (isGroupAddressInFilterTable(groupAddress)) + ;//send + else + { + //printHex("4drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + } + + break; + + default: // LCGRPCONFIG::GROUP_7000UNLOCK + ;//send + } + } + + return true; +} + +bool NetworkLayerCoupler::isRoutedIndividualAddress(uint16_t individualAddress, uint8_t srcIfIndex) +{ + // TODO: improve: we have to be notified about anything that might affect routing decision + // Ugly: we could ALWAYS evaluate coupler type for every received frame + if (_currentAddress != _deviceObj.individualAddress()) + { + evaluateCouplerType(); + } + + // See KNX spec.: Network Layer (03/03/03) and AN161 (Coupler model 2.0) + /* + * C hop count value contained in the N-protocol header + * D low order octet of the Destination Address, i.e. Device Address part + * G Group Address + * SD low nibble of high order octet plus low order octet, i.e. Line Address + Device Address + * Z high nibble of high order octet of the Destination Address, i.e. Area Address + * ZS high order octet of the Destination Address, i.e. hierarchy information part: Area Address + Line Address + */ + uint16_t ownSNA = _deviceObj.individualAddress() & 0xFF00; // Own subnetwork address (area + line) + uint16_t ownAA = _deviceObj.individualAddress() & 0xF000; // Own area address + uint16_t ZS = individualAddress & 0xFF00; // destination subnetwork address (area + line) + uint16_t Z = individualAddress & 0xF000; // destination area address + + + if (_couplerType == LineCoupler) + { + // Main line to sub line routing + if (srcIfIndex == kPrimaryIfIndex) + { + if (ZS != ownSNA) + { + // IGNORE_TOTALLY + return false; + } + + return true; + } + else if (srcIfIndex == kSecondaryIfIndex) // Sub line to main line routing + { + if (ZS != ownSNA) + { + // ROUTE_XXX + return true; + } + else + { + return false; + } + } + else + { + //not from primiary not from sec if, should not happen + return false; + } + } + else if (_couplerType == BackboneCoupler) + { + // Backbone line to main line routing + if (srcIfIndex == kPrimaryIfIndex) + { + if (Z != ownAA) + { + return false; + } + + return true; + } + else if (srcIfIndex == kSecondaryIfIndex) // Main line to backbone line routing + { + if (Z != ownAA) + { + return true; + } + else + { + return false; + } + } + else + { + //not from primiary not from sec if, should not happen + return false; + } + } + else + { + //unknown coupler type, should not happen + return false; + } +} + +void NetworkLayerCoupler::sendMsgHopCount(AckType ack, AddressType addrType, uint16_t destination, NPDU& npdu, Priority priority, + SystemBroadcast broadcastType, uint8_t sourceInterfaceIndex, uint16_t source) +{ + uint8_t interfaceIndex = (sourceInterfaceIndex == kSecondaryIfIndex) ? kPrimaryIfIndex : kSecondaryIfIndex; + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + uint8_t lcgrpconfig = LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT; // default value from spec. in case prop is not availible. + Property* prop_lcgrpconfig; + Property* prop_lcconfig; + + if (sourceInterfaceIndex == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_MAIN_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + } + else // direction Sec -> Prim ( e.g. TP -> IP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_SUB_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + } + + if (prop_lcgrpconfig) + prop_lcgrpconfig->read(lcgrpconfig); + + if (prop_lcconfig) + prop_lcconfig->read(lcconfig); + + + if (addrType == AddressType::GroupAddress && destination != 0) // destination == 0 means broadcast and must not be filtered with the GroupAddresses + { + if (!isRoutedGroupAddress(destination, sourceInterfaceIndex)) + return; // drop; + } + + + // If we have a frame from open medium on secondary side (e.g. RF) to primary side, then shall use the hop count of the primary router object + if ((_rtObjPrimary != nullptr) && (_rtObjSecondary != nullptr) && (sourceInterfaceIndex == kSecondaryIfIndex)) + { + DptMedium mediumType = getSecondaryInterface().mediumType(); + + if (mediumType == DptMedium::KNX_RF) // Probably also KNX_PL110, but this is not specified, PL110 is also an open medium + { + uint16_t hopCount = 0; + + if (_rtObjPrimary->property(PID_HOP_COUNT)->read(hopCount) == 1) + { + npdu.hopCount(hopCount); + } + } + } + else // Normal hopCount between main and sub line and vice versa + { + if (npdu.hopCount() == 0) + { + // IGNORE_ACKED + return; + } + + if (npdu.hopCount() < 7) + { + // ROUTE_DECREMENTED + npdu.hopCount(npdu.hopCount() - 1); + } + else if (npdu.hopCount() == 7) + { + // ROUTE_UNMODIFIED + } + } + + // Use other interface +#ifdef KNX_LOG_COUPLER + + if (sourceInterfaceIndex == 0) + print("Routing from P->S: "); + else + print("Routing from S->P: "); + + print(source, HEX); + print(" -> "); + print(destination, HEX); + print(" - "); + npdu.frame().apdu().printPDU(); +#endif + + //evaluiate PHYS_REPEAT, BROADCAST_REPEAT and GROUP_REPEAT + bool doNotRepeat = false; + + if ((addrType == AddressType::GroupAddress && !(lcgrpconfig & LCGRPCONFIG::GROUP_REPEAT)) || + (addrType == AddressType::IndividualAddress && !(lcconfig & LCCONFIG::PHYS_REPEAT)) || + (addrType == AddressType::GroupAddress && destination == 0 && !(lcconfig & LCCONFIG::BROADCAST_REPEAT))) + doNotRepeat = true; + + _netLayerEntities[interfaceIndex].sendDataRequest(npdu, ack, destination, source, priority, addrType, broadcastType, doNotRepeat); +} + +// TODO: for later: improve by putting routing algorithms in its own class/functions and only instantiate required algorithm (line vs. coupler) +// TODO: we could also do the sanity checks here, i.e. check if sourceAddress is really coming in from correct srcIfIdx, etc. (see PID_COUPL_SERV_CONTROL: EN_SNA_INCONSISTENCY_CHECK) +void NetworkLayerCoupler::routeDataIndividual(AckType ack, uint16_t destination, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIndex) +{ + //print("NetworkLayerCoupler::routeDataIndividual dest 0x"); + //print(destination, HEX); + //print(" own addr 0x"); + //println(_deviceObj.individualAddress(), HEX); + + if (destination == _deviceObj.individualAddress()) + { + // FORWARD_LOCALLY + //println("NetworkLayerCoupler::routeDataIndividual locally"); + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataIndividualIndication(destination, hopType, priority, source, npdu.tpdu()); + return; + } + + // Local to main or sub line + if (srcIfIndex == kLocalIfIndex) + { + uint16_t netaddr; + uint16_t Z; + + if (_couplerType == CouplerType::BackboneCoupler) + { + netaddr = _deviceObj.individualAddress() & 0xF000; + Z = destination & 0xF000; + } + else if (_couplerType == CouplerType::LineCoupler) + { + netaddr = _deviceObj.individualAddress() & 0xFF00; + Z = destination & 0xFF00; + } + else + { + //unknown coupler type, should not happen + return ; + } + + + // if destination is not within our scope then send via primary interface, else via secondary interface + uint8_t destIfidx = (Z != netaddr) ? kPrimaryIfIndex : kSecondaryIfIndex; +#ifdef KNX_TUNNELING + + if (destIfidx == kPrimaryIfIndex) + if (isTunnelAddress(destination)) + destIfidx = kSecondaryIfIndex; + +#endif + //print("NetworkLayerCoupler::routeDataIndividual local to s or p: "); + //println(destIfidx); + _netLayerEntities[destIfidx].sendDataRequest(npdu, ack, destination, source, priority, AddressType::IndividualAddress, Broadcast); + return; + } + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig; + + if (srcIfIndex == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + else // direction Sec -> Prim ( e.g. TP -> IP) + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + + if (prop_lcconfig) + prop_lcconfig->read(lcconfig); + + if ((lcconfig & LCCONFIG::PHYS_FRAME) == LCCONFIG::PHYS_FRAME_LOCK) + { + // IGNORE_TOTALLY + //println("NetworkLayerCoupler::routeDataIndividual locked"); + return; + } + else if ((lcconfig & LCCONFIG::PHYS_FRAME) == LCCONFIG::PHYS_FRAME_UNLOCK) + { + // ROUTE_XXX + //println("NetworkLayerCoupler::routeDataIndividual unlocked"); + sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); + return; + } + else // LCCONFIG::PHYS_FRAME_ROUTE or 0 + { + if (isRoutedIndividualAddress(destination, srcIfIndex)) + { + //println("NetworkLayerCoupler::routeDataIndividual routed"); + sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); // ROUTE_XXX + } + else + { + //println("NetworkLayerCoupler::routeDataIndividual not routed"); + ; // IGNORE_TOTALLY + } + } +} + +void NetworkLayerCoupler::dataIndication(AckType ack, AddressType addrType, uint16_t destination, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIdx) +{ + // routing for individual addresses + if (addrType == IndividualAddress) + { + //printHex("NetworkLayerCoupler::dataIndication to IA ", (uint8_t*)&destination, 2); + //npdu.frame().valid(); + routeDataIndividual(ack, destination, npdu, priority, source, srcIfIdx); + return; + } + + //printHex("NetworkLayerCoupler::dataIndication to GA ", (uint8_t*)&destination, 2); + // routing for group addresses + // TODO: check new AN189 + // "AN189 only makes that group messages with hop count 7 cannot bypass the Filter Table unfiltered, + // what made the Security Proxy(AN192) useless; now, hc 7 Telegrams are filtered as any other and the value is decremented. + + // ROUTE_XXX + sendMsgHopCount(ack, addrType, destination, npdu, priority, Broadcast, srcIfIdx, source); + return; +} + +void NetworkLayerCoupler::dataConfirm(AckType ack, AddressType addrType, uint16_t destination, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) +{ + //println("NetworkLayerCoupler::dataConfirm"); + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + + // Check if received frame is an echo from our sent frame, we are a normal device in this case + if (source == _deviceObj.individualAddress()) + { + if (addrType == IndividualAddress) + { + _transportLayer.dataIndividualConfirm(ack, destination, hopType, priority, npdu.tpdu(), status); + return; + } + + // else: we do not have any local group communication, so do not handle this + } + + // Do not process the frame any further if it was a routed frame sent from network layer +} + +void NetworkLayerCoupler::broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIdx) +{ + // Send it to our local stack first + { + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + DptMedium mediumType = _netLayerEntities[srcIfIdx].mediumType(); + + // for closed media like TP1 and IP + if ( ((mediumType == DptMedium::KNX_TP1) || (mediumType == DptMedium::KNX_IP)) && + isApciSystemBroadcast(npdu.tpdu().apdu())) + { + npdu.frame().systemBroadcast(SysBroadcast); + _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu()); + return; + } + + _transportLayer.dataBroadcastIndication(hopType, priority, source, npdu.tpdu()); + } + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig; + + if (srcIfIdx == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + else // direction Sec -> Prim ( e.g. TP -> IP) + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + + if (prop_lcconfig) + prop_lcconfig->read(lcconfig); + + // Route to other interface + if (!(lcconfig & LCCONFIG::BROADCAST_LOCK)) + sendMsgHopCount(ack, GroupAddress, 0, npdu, priority, Broadcast, srcIfIdx, source); +} + +void NetworkLayerCoupler::broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + + // Check if received frame is an echo from our sent frame, we are a normal device in this case + if (source == _deviceObj.individualAddress()) + { + _transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status); + } + + // Do not process the frame any further +} + +void NetworkLayerCoupler::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIdx) +{ + // Send it to our local stack first + { + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu()); + } + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig; + + if (srcIfIdx == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + else // direction Sec -> Prim ( e.g. TP -> IP) + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + + if (prop_lcconfig) + prop_lcconfig->read(lcconfig); + + // Route to other interface + if (!(lcconfig & LCCONFIG::BROADCAST_LOCK)) + sendMsgHopCount(ack, GroupAddress, 0, npdu, priority, SysBroadcast, srcIfIdx, source); +} + +void NetworkLayerCoupler::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) +{ + // Check if received frame is an echo from our sent frame, we are a normal device in this case + if (source == _deviceObj.individualAddress()) + { + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataSystemBroadcastConfirm(ack, hopType, npdu.tpdu(), priority, status); + } + + // Do not process the frame any further +} + +void NetworkLayerCoupler::dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + //if (tpdu.apdu().length() > 0) + //{ + // print.print("-> NL "); + // tpdu.apdu().printPDU(); + //} + routeDataIndividual(ack, destination, npdu, priority, _deviceObj.individualAddress(), kLocalIfIndex); +} + +void NetworkLayerCoupler::dataGroupRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + // If the group address is in the filter table, then we route it to the primary side too + if (isGroupAddressInFilterTable(destination)) + { + _netLayerEntities[kPrimaryIfIndex].sendDataRequest(npdu, ack, destination, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); + } + + // We send it to our sub line in any case + _netLayerEntities[kSecondaryIfIndex].sendDataRequest(npdu, ack, destination, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); +} + +void NetworkLayerCoupler::dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + CemiFrame tmpFrame(tpdu.frame()); + + _netLayerEntities[kPrimaryIfIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); + _netLayerEntities[kSecondaryIfIndex].sendDataRequest(tmpFrame.npdu(), ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); +} + +void NetworkLayerCoupler::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + + CemiFrame tmpFrame(tpdu.frame()); + + // for closed media like TP1 and IP + bool isClosedMedium = (_netLayerEntities[kPrimaryIfIndex].mediumType() == DptMedium::KNX_TP1) || (_netLayerEntities[kPrimaryIfIndex].mediumType() == DptMedium::KNX_IP); + SystemBroadcast broadcastType = (isClosedMedium && isApciSystemBroadcast(tpdu.apdu()) ? Broadcast : SysBroadcast); + _netLayerEntities[kPrimaryIfIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, broadcastType); + + isClosedMedium = (_netLayerEntities[kSecondaryIfIndex].mediumType() == DptMedium::KNX_TP1) || (_netLayerEntities[kSecondaryIfIndex].mediumType() == DptMedium::KNX_IP); + broadcastType = (isClosedMedium && isApciSystemBroadcast(tmpFrame.apdu()) ? Broadcast : SysBroadcast); + println(broadcastType); + _netLayerEntities[kSecondaryIfIndex].sendDataRequest(tmpFrame.npdu(), ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, broadcastType); +} + +#ifdef KNX_TUNNELING +bool NetworkLayerCoupler::isTunnelAddress(uint16_t destination) +{ + // tunnels are managed within the IpDataLinkLayer - kPrimaryIfIndex + return _netLayerEntities[kPrimaryIfIndex].dataLinkLayer().isTunnelAddress(destination); +} +#endif \ No newline at end of file diff --git a/components/knx/src/knx/network_layer_coupler.h b/components/knx/src/knx/network_layer_coupler.h new file mode 100644 index 0000000..75f1694 --- /dev/null +++ b/components/knx/src/knx/network_layer_coupler.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include "knx_types.h" +#include "npdu.h" +#include "transport_layer.h" +#include "network_layer_entity.h" +#include "network_layer.h" + +class DeviceObject; +class RouterObject; + +class NetworkLayerCoupler : public NetworkLayer +{ + friend class NetworkLayerEntity; + + public: + NetworkLayerCoupler(DeviceObject& deviceObj, TransportLayer& layer); + + NetworkLayerEntity& getPrimaryInterface(); + NetworkLayerEntity& getSecondaryInterface(); + + bool isRoutedIndividualAddress(uint16_t individualAddress, uint8_t srcIfIndex); + + bool isRoutedGroupAddress(uint16_t groupAddress, uint8_t sourceInterfaceIndex); + + void rtObjPrimary(RouterObject& rtObjPrimary); // Coupler model 2.0 + void rtObjSecondary(RouterObject& rtObjSecondary); // Coupler model 2.0 + void rtObj(RouterObject& rtObj); // Coupler model 1.x + + // from transport layer + void dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) override; + void dataGroupRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) override; + void dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) override; + void dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) override; + + private: + enum CouplerType + { + LineCoupler, + BackboneCoupler, + TP1Bridge, + TP1Repeater + }; + + static constexpr uint8_t kPrimaryIfIndex = 0; + static constexpr uint8_t kSecondaryIfIndex = 1; + static constexpr uint8_t kLocalIfIndex = 99; + + // from entities + void dataIndication(AckType ack, AddressType addType, uint16_t destination, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) override; + void dataConfirm(AckType ack, AddressType addrType, uint16_t destination, FrameFormat format, Priority priority, + uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) override; + void broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) override; + void broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) override; + void systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) override; + void systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) override; + + void routeDataIndividual(AckType ack, uint16_t destination, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIndex); + void sendMsgHopCount(AckType ack, AddressType addrType, uint16_t destination, NPDU& npdu, Priority priority, + SystemBroadcast broadcastType, uint8_t sourceInterfaceIndex, uint16_t source); + + void evaluateCouplerType(); + bool isGroupAddressInFilterTable(uint16_t groupAddress); +#ifdef KNX_TUNNELING + bool isTunnelAddress(uint16_t destination); +#endif + + // Support a maximum of two physical interfaces for couplers + NetworkLayerEntity _netLayerEntities[2]; + + RouterObject* _rtObjPrimary {nullptr}; + RouterObject* _rtObjSecondary {nullptr}; + + CouplerType _couplerType; + uint16_t _currentAddress; +}; diff --git a/components/knx/src/knx/network_layer_device.cpp b/components/knx/src/knx/network_layer_device.cpp new file mode 100644 index 0000000..105003e --- /dev/null +++ b/components/knx/src/knx/network_layer_device.cpp @@ -0,0 +1,150 @@ +#include "network_layer_device.h" +#include "device_object.h" +#include "tpdu.h" +#include "cemi_frame.h" +#include "bits.h" + +NetworkLayerDevice::NetworkLayerDevice(DeviceObject& deviceObj, TransportLayer& layer) : + NetworkLayer(deviceObj, layer), + _netLayerEntities { {*this, kInterfaceIndex} } +{ +} + +NetworkLayerEntity& NetworkLayerDevice::getInterface() +{ + return _netLayerEntities[kInterfaceIndex]; +} + +void NetworkLayerDevice::dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + //if (tpdu.apdu().length() > 0) + //{ + // print.print("-> NL "); + // tpdu.apdu().printPDU(); + //} + _netLayerEntities[kInterfaceIndex].sendDataRequest(npdu, ack, destination, _deviceObj.individualAddress(), priority, IndividualAddress, Broadcast); +} + +void NetworkLayerDevice::dataGroupRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + _netLayerEntities[kInterfaceIndex].sendDataRequest(npdu, ack, destination, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); +} + +void NetworkLayerDevice::dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + _netLayerEntities[kInterfaceIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); +} + +void NetworkLayerDevice::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) +{ + // for closed media like TP1 and IP + bool isClosedMedium = (_netLayerEntities[kInterfaceIndex].mediumType() == DptMedium::KNX_TP1) || (_netLayerEntities[kInterfaceIndex].mediumType() == DptMedium::KNX_IP); + SystemBroadcast broadcastType = (isClosedMedium && isApciSystemBroadcast(tpdu.apdu()) ? Broadcast : SysBroadcast); + + NPDU& npdu = tpdu.frame().npdu(); + + if (hopType == UnlimitedRouting) + npdu.hopCount(7); + else + npdu.hopCount(hopCount()); + + _netLayerEntities[kInterfaceIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, broadcastType); +} + +void NetworkLayerDevice::dataIndication(AckType ack, AddressType addrType, uint16_t destination, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIdx) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + + if (addrType == IndividualAddress) + { + if (destination != _deviceObj.individualAddress()) + return; + + _transportLayer.dataIndividualIndication(destination, hopType, priority, source, npdu.tpdu()); + return; + } + + // group-address type + if (destination != 0) + { + _transportLayer.dataGroupIndication(destination, hopType, priority, source, npdu.tpdu()); + return; + } +} + +void NetworkLayerDevice::dataConfirm(AckType ack, AddressType addressType, uint16_t destination, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + + if (addressType == IndividualAddress) + { + _transportLayer.dataIndividualConfirm(ack, destination, hopType, priority, npdu.tpdu(), status); + return; + } + + // group-address type + if (destination != 0) + { + _transportLayer.dataGroupConfirm(ack, source, destination, hopType, priority, npdu.tpdu(), status); + return; + } +} + +void NetworkLayerDevice::broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIdx) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + DptMedium mediumType = _netLayerEntities[srcIfIdx].mediumType(); + + // for closed media like TP1 and IP there is no system broadcast + // however we must be able to access those APCI via broadcast mode + // so we "translate" it to system broadcast like a coupler does when routing + // between closed and open media + if ( ((mediumType == DptMedium::KNX_TP1) || (mediumType == DptMedium::KNX_IP)) && + isApciSystemBroadcast(npdu.tpdu().apdu())) + { + npdu.frame().systemBroadcast(SysBroadcast); + _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu()); + return; + } + + _transportLayer.dataBroadcastIndication(hopType, priority, source, npdu.tpdu()); +} + +void NetworkLayerDevice::broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status); +} + +void NetworkLayerDevice::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIdx) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu()); +} + +void NetworkLayerDevice::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) +{ + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataSystemBroadcastConfirm(ack, hopType, npdu.tpdu(), priority, status); +} diff --git a/components/knx/src/knx/network_layer_device.h b/components/knx/src/knx/network_layer_device.h new file mode 100644 index 0000000..4833dd0 --- /dev/null +++ b/components/knx/src/knx/network_layer_device.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include "knx_types.h" +#include "npdu.h" +#include "transport_layer.h" +#include "network_layer_entity.h" +#include "network_layer.h" + +class DeviceObject; + +class NetworkLayerDevice : public NetworkLayer +{ + friend class NetworkLayerEntity; + + public: + NetworkLayerDevice(DeviceObject& deviceObj, TransportLayer& layer); + + NetworkLayerEntity& getInterface(); + + // from transport layer + void dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) override; + void dataGroupRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu) override; + void dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) override; + void dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) override; + + private: + // from entities + void dataIndication(AckType ack, AddressType addType, uint16_t destination, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) override; + void dataConfirm(AckType ack, AddressType addrType, uint16_t destination, FrameFormat format, Priority priority, + uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) override; + void broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) override; + void broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) override; + void systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source, uint8_t srcIfIdx) override; + void systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) override; + + // Support only a single physical interface for normal devices + NetworkLayerEntity _netLayerEntities[1]; + + static constexpr uint8_t kInterfaceIndex = 0; +}; diff --git a/components/knx/src/knx/network_layer_entity.cpp b/components/knx/src/knx/network_layer_entity.cpp new file mode 100644 index 0000000..925dd52 --- /dev/null +++ b/components/knx/src/knx/network_layer_entity.cpp @@ -0,0 +1,74 @@ +#include "network_layer.h" +#include "network_layer_entity.h" +#include "tpdu.h" +#include "data_link_layer.h" +#include "bits.h" + +NetworkLayerEntity::NetworkLayerEntity(NetworkLayer& netLayer, uint8_t entityIndex) : _netLayer(netLayer), _entityIndex(entityIndex) +{ +} + +void NetworkLayerEntity::dataLinkLayer(DataLinkLayer& layer) +{ + _dataLinkLayer = &layer; +} + +DataLinkLayer& NetworkLayerEntity::dataLinkLayer() +{ + return *_dataLinkLayer; +} + +NetworkLayer& NetworkLayerEntity::networkLayer() +{ + return _netLayer; +} + +DptMedium NetworkLayerEntity::mediumType() const +{ + return _dataLinkLayer->mediumType(); +} + +uint8_t NetworkLayerEntity::getEntityIndex() +{ + return _entityIndex; +} + +void NetworkLayerEntity::dataIndication(AckType ack, AddressType addrType, uint16_t destination, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source) +{ + _netLayer.dataIndication(ack, addrType, destination, format, npdu, priority, source, _entityIndex); +} + +void NetworkLayerEntity::dataConfirm(AckType ack, AddressType addressType, uint16_t destination, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status) +{ + _netLayer.dataConfirm(ack, addressType, destination, format, priority, source, npdu, status, _entityIndex); +} + +void NetworkLayerEntity::broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source) +{ + _netLayer.broadcastIndication(ack, format, npdu, priority, source, _entityIndex); +} + +void NetworkLayerEntity::broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status) +{ + _netLayer.broadcastConfirm(ack, format, priority, source, npdu, status, _entityIndex); +} + +void NetworkLayerEntity::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source) +{ + _netLayer.systemBroadcastIndication(ack, format, npdu, priority, source, _entityIndex); +} + +void NetworkLayerEntity::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status) +{ + _netLayer.systemBroadcastConfirm(ack, format, priority, source, npdu, status, _entityIndex); +} + +void NetworkLayerEntity::sendDataRequest(NPDU& npdu, AckType ack, uint16_t destination, uint16_t source, Priority priority, AddressType addrType, SystemBroadcast systemBroadcast, bool doNotRepeat) +{ + FrameFormat frameFormat = npdu.octetCount() > 15 ? ExtendedFrame : StandardFrame; + + if (systemBroadcast == Broadcast) + _dataLinkLayer->dataRequest(ack, addrType, destination, source, frameFormat, priority, npdu); + else + _dataLinkLayer->systemBroadcastRequest(ack, frameFormat, priority, npdu, source); +} diff --git a/components/knx/src/knx/network_layer_entity.h b/components/knx/src/knx/network_layer_entity.h new file mode 100644 index 0000000..0c579c1 --- /dev/null +++ b/components/knx/src/knx/network_layer_entity.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include "knx_types.h" +#include "npdu.h" + +class DataLinkLayer; +class NetworkLayer; + +class NetworkLayerEntity +{ + friend class NetworkLayerCoupler; + friend class NetworkLayerDevice; + + public: + NetworkLayerEntity(NetworkLayer& netLayer, uint8_t entityIndex); + + void dataLinkLayer(DataLinkLayer& layer); + DataLinkLayer& dataLinkLayer(); + NetworkLayer& networkLayer(); + + DptMedium mediumType() const; + uint8_t getEntityIndex(); + + // from data link layer + void dataIndication(AckType ack, AddressType addType, uint16_t destination, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source); + void dataConfirm(AckType ack, AddressType addressType, uint16_t destination, FrameFormat format, Priority priority, + uint16_t source, NPDU& npdu, bool status); + void broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source); + void broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status); + void systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, + Priority priority, uint16_t source); + void systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status); + + private: + // From network layer + void sendDataRequest(NPDU& npdu, AckType ack, uint16_t destination, uint16_t source, Priority priority, AddressType addrType, SystemBroadcast systemBroadcast, bool doNotRepeat = false); + + DataLinkLayer* _dataLinkLayer = 0; + NetworkLayer& _netLayer; + uint8_t _entityIndex; +}; diff --git a/components/knx/src/knx/npdu.cpp b/components/knx/src/knx/npdu.cpp new file mode 100644 index 0000000..a5b840c --- /dev/null +++ b/components/knx/src/knx/npdu.cpp @@ -0,0 +1,44 @@ +#include "npdu.h" +#include "cemi_frame.h" +#include + + +NPDU::NPDU(uint8_t* data, CemiFrame& frame): _data(data), _frame(frame) +{ +} + + +uint8_t NPDU::octetCount() const +{ + return _data[0]; +} + +void NPDU::octetCount(uint8_t value) +{ + _data[0] = value; +} + +uint8_t NPDU::length() const +{ + return _data[0] + 2; // +1 for length field, +1 for TCPI +} + +uint8_t NPDU::hopCount() const +{ + return _frame.hopCount(); +} + +void NPDU::hopCount(uint8_t value) +{ + _frame.hopCount(value); +} + +CemiFrame& NPDU::frame() +{ + return _frame; +} + +TPDU& NPDU::tpdu() +{ + return _frame.tpdu(); +} diff --git a/components/knx/src/knx/npdu.h b/components/knx/src/knx/npdu.h new file mode 100644 index 0000000..bfa52ca --- /dev/null +++ b/components/knx/src/knx/npdu.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class CemiFrame; +class TPDU; + +class NPDU +{ + friend class CemiFrame; + + public: + uint8_t octetCount() const; + void octetCount(uint8_t value); + uint8_t length() const; + uint8_t hopCount() const; + void hopCount(uint8_t value); + CemiFrame& frame(); + TPDU& tpdu(); + + protected: + NPDU(uint8_t* data, CemiFrame& frame); + + private: + uint8_t* _data = 0; + CemiFrame& _frame; +}; \ No newline at end of file diff --git a/components/knx/src/knx/platform.cpp b/components/knx/src/knx/platform.cpp new file mode 100644 index 0000000..a98d2e9 --- /dev/null +++ b/components/knx/src/knx/platform.cpp @@ -0,0 +1,451 @@ +#include "platform.h" + +#include "bits.h" + +#include +#include + +NvMemoryType Platform::NonVolatileMemoryType() +{ + return _memoryType; +} + +void Platform::NonVolatileMemoryType(NvMemoryType type) +{ + _memoryType = type; +} + +void Platform::setupSpi() +{} + +void Platform::closeSpi() +{} + +int Platform::readWriteSpi(uint8_t* data, size_t len) +{ + return 0; +} + +size_t Platform::readBytesUart(uint8_t* buffer, size_t length) +{ + return 0; +} + +int Platform::readUart() +{ + return -1; +} + +size_t Platform::writeUart(const uint8_t* buffer, size_t size) +{ + return 0; +} + +size_t Platform::writeUart(const uint8_t data) +{ + return 0; +} + +int Platform::uartAvailable() +{ + return 0; +} + +void Platform::closeUart() +{} + +void Platform::setupUart() +{} + +bool Platform::overflowUart() +{ + return false; +} + +void Platform::flushUart() +{} + +uint32_t Platform::currentIpAddress() +{ + return 0x01020304; +} + +uint32_t Platform::currentSubnetMask() +{ + return 0; +} + +uint32_t Platform::currentDefaultGateway() +{ + return 0; +} + +void Platform::macAddress(uint8_t* data) +{} + +uint32_t Platform::uniqueSerialNumber() +{ + return 0x01020304; +} + +void Platform::setupMultiCast(uint32_t addr, uint16_t port) +{} + +void Platform::closeMultiCast() +{} + +bool Platform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) +{ + return false; +} + +bool Platform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) +{ + return false; +} + +int Platform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) +{ + return 0; +} + +int Platform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) +{ + return readBytesMultiCast(buffer, maxLen); +} + +size_t Platform::flashEraseBlockSize() +{ + return 0; +} + +size_t Platform::flashPageSize() +{ + // align to 32bit as default for Eeprom Emulation plattforms + return 4; +} + +uint8_t* Platform::userFlashStart() +{ + return nullptr; +} + +size_t Platform::userFlashSizeEraseBlocks() +{ + return 0; +} + +void Platform::flashErase(uint16_t eraseBlockNum) +{} + +void Platform::flashWritePage(uint16_t pageNumber, uint8_t* data) +{} + +uint8_t* Platform::getEepromBuffer(uint32_t size) +{ + return nullptr; +} + +void Platform::commitToEeprom() +{} + +uint8_t* Platform::getNonVolatileMemoryStart() +{ + if (_memoryType == Flash) + return userFlashStart(); + +#ifdef KNX_FLASH_CALLBACK + else if (_memoryType == Callback) + return _callbackFlashRead(); + +#endif + else + return getEepromBuffer(KNX_FLASH_SIZE); +} + +size_t Platform::getNonVolatileMemorySize() +{ + if (_memoryType == Flash) + return userFlashSizeEraseBlocks() * flashEraseBlockSize() * flashPageSize(); + +#ifdef KNX_FLASH_CALLBACK + else if (_memoryType == Callback) + return _callbackFlashSize(); + +#endif + else + return KNX_FLASH_SIZE; +} + +void Platform::commitNonVolatileMemory() +{ + if (_memoryType == Flash) + { + if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) + { + writeBufferedEraseBlock(); + + free(_eraseblockBuffer); + _eraseblockBuffer = nullptr; + _bufferedEraseblockNumber = -1; // does that make sense? + } + } + +#ifdef KNX_FLASH_CALLBACK + else if (_memoryType == Callback) + return _callbackFlashCommit(); + +#endif + else + { + commitToEeprom(); + } +} + +uint32_t Platform::writeNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size) +{ +#ifdef KNX_LOG_MEM + print("Platform::writeNonVolatileMemory relativeAddress "); + print(relativeAddress); + print(" size "); + println(size); +#endif + + if (_memoryType == Flash) + { + while (size > 0) + { + loadEraseblockContaining(relativeAddress); + uint32_t start = _bufferedEraseblockNumber * (flashEraseBlockSize() * flashPageSize()); + uint32_t end = start + (flashEraseBlockSize() * flashPageSize()); + + uint32_t offset = relativeAddress - start; + uint32_t length = end - relativeAddress; + + if (length > size) + length = size; + + memcpy(_eraseblockBuffer + offset, buffer, length); + _bufferedEraseblockDirty = true; + + relativeAddress += length; + buffer += length; + size -= length; + } + + return relativeAddress; + } + +#ifdef KNX_FLASH_CALLBACK + else if (_memoryType == Callback) + return _callbackFlashWrite(relativeAddress, buffer, size); + +#endif + else + { + memcpy(getEepromBuffer(KNX_FLASH_SIZE) + relativeAddress, buffer, size); + return relativeAddress + size; + } +} + +uint32_t Platform::readNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size) +{ +#ifdef KNX_LOG_MEM + print("Platform::readNonVolatileMemory relativeAddress "); + print(relativeAddress); + print(" size "); + println(size); +#endif + + if (_memoryType == Flash) + { + uint32_t offset = 0; + + while (size > 0) + { + // bufferd block is "left" of requested memory, read until the end and return + if (_bufferedEraseblockNumber < getEraseBlockNumberOf(relativeAddress)) + { + memcpy(buffer + offset, userFlashStart() + relativeAddress, size); + return relativeAddress + size; + } + // bufferd block is "right" of requested memory, and may interfere + else if (_bufferedEraseblockNumber > getEraseBlockNumberOf(relativeAddress)) + { + // if the end of the requested memory is before the buffered block, read until the end and return + int32_t eraseblockNumberEnd = getEraseBlockNumberOf(relativeAddress + size - 1); + + if (_bufferedEraseblockNumber > eraseblockNumberEnd) + { + memcpy(buffer + offset, userFlashStart() + relativeAddress, size); + return relativeAddress + size; + } + // if not, read until the buffered block starts and loop through while again + else + { + uint32_t sizeToRead = (eraseblockNumberEnd * flashEraseBlockSize()) - relativeAddress; + memcpy(buffer + offset, userFlashStart() + relativeAddress, sizeToRead); + relativeAddress += sizeToRead; + size -= sizeToRead; + offset += sizeToRead; + } + } + // start of requested memory is within the buffered erase block + else + { + // if the end of the requested memory is also in the buffered block, read until the end and return + int32_t eraseblockNumberEnd = getEraseBlockNumberOf(relativeAddress + size - 1); + + if (_bufferedEraseblockNumber == eraseblockNumberEnd) + { + uint8_t* start = _eraseblockBuffer + (relativeAddress - _bufferedEraseblockNumber * flashEraseBlockSize()); + memcpy(buffer + offset, start, size); + return relativeAddress + size; + } + // if not, read until the end of the buffered block and loop through while again + else + { + uint32_t offsetInBufferedBlock = relativeAddress - _bufferedEraseblockNumber * flashEraseBlockSize(); + uint8_t* start = _eraseblockBuffer + offsetInBufferedBlock; + uint32_t sizeToRead = flashEraseBlockSize() - offsetInBufferedBlock; + memcpy(buffer + offset, start, sizeToRead); + relativeAddress += sizeToRead; + size -= sizeToRead; + offset += sizeToRead; + } + } + } + + return relativeAddress; + } + else + { + memcpy(buffer, getEepromBuffer(KNX_FLASH_SIZE) + relativeAddress, size); + return relativeAddress + size; + } +} + +// writes value repeat times into flash starting at relativeAddress +// returns next free relativeAddress +uint32_t Platform::writeNonVolatileMemory(uint32_t relativeAddress, uint8_t value, size_t repeat) +{ + if (_memoryType == Flash) + { + while (repeat > 0) + { + loadEraseblockContaining(relativeAddress); + uint32_t start = _bufferedEraseblockNumber * (flashEraseBlockSize() * flashPageSize()); + uint32_t end = start + (flashEraseBlockSize() * flashPageSize()); + + uint32_t offset = relativeAddress - start; + uint32_t length = end - relativeAddress; + + if (length > repeat) + length = repeat; + + memset(_eraseblockBuffer + offset, value, length); + _bufferedEraseblockDirty = true; + + relativeAddress += length; + repeat -= length; + } + + return relativeAddress; + } + else + { + memset(getEepromBuffer(KNX_FLASH_SIZE) + relativeAddress, value, repeat); + return relativeAddress + repeat; + } +} + +void Platform::loadEraseblockContaining(uint32_t relativeAddress) +{ + int32_t blockNum = getEraseBlockNumberOf(relativeAddress); + + if (blockNum < 0) + { + println("loadEraseblockContaining could not get valid eraseblock number"); + fatalError(); + } + + if (blockNum != _bufferedEraseblockNumber && _bufferedEraseblockNumber >= 0) + writeBufferedEraseBlock(); + + bufferEraseBlock(blockNum); +} + +int32_t Platform::getEraseBlockNumberOf(uint32_t relativeAddress) +{ + return relativeAddress / (flashEraseBlockSize() * flashPageSize()); +} + + +void Platform::writeBufferedEraseBlock() +{ + if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) + { + flashErase(_bufferedEraseblockNumber); + + for (uint32_t i = 0; i < flashEraseBlockSize(); i++) + { + int32_t pageNumber = _bufferedEraseblockNumber * flashEraseBlockSize() + i; + uint8_t* data = _eraseblockBuffer + flashPageSize() * i; + flashWritePage(pageNumber, data); + } + + _bufferedEraseblockDirty = false; + } +} + + +void Platform::bufferEraseBlock(int32_t eraseBlockNumber) +{ + if (_bufferedEraseblockNumber == eraseBlockNumber) + return; + + if (_eraseblockBuffer == nullptr) + { + _eraseblockBuffer = (uint8_t*)malloc(flashEraseBlockSize() * flashPageSize()); + } + + memcpy(_eraseblockBuffer, userFlashStart() + eraseBlockNumber * flashEraseBlockSize() * flashPageSize(), flashEraseBlockSize() * flashPageSize()); + + _bufferedEraseblockNumber = eraseBlockNumber; + _bufferedEraseblockDirty = false; +} + + +#ifdef KNX_FLASH_CALLBACK +void Platform::registerFlashCallbacks( + FlashCallbackSize callbackFlashSize, + FlashCallbackRead callbackFlashRead, + FlashCallbackWrite callbackFlashWrite, + FlashCallbackCommit callbackFlashCommit) +{ + println("Set Callback"); + _memoryType = Callback; + _callbackFlashSize = callbackFlashSize; + _callbackFlashRead = callbackFlashRead; + _callbackFlashWrite = callbackFlashWrite; + _callbackFlashCommit = callbackFlashCommit; + _callbackFlashSize(); +} + +FlashCallbackSize Platform::callbackFlashSize() +{ + return _callbackFlashSize; +} +FlashCallbackRead Platform::callbackFlashRead() +{ + return _callbackFlashRead; +} +FlashCallbackWrite Platform::callbackFlashWrite() +{ + return _callbackFlashWrite; +} +FlashCallbackCommit Platform::callbackFlashCommit() +{ + return _callbackFlashCommit; +} +#endif diff --git a/components/knx/src/knx/platform.h b/components/knx/src/knx/platform.h new file mode 100644 index 0000000..d60af19 --- /dev/null +++ b/components/knx/src/knx/platform.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include "save_restore.h" + +#ifndef KNX_FLASH_CALLBACK + #ifndef KNX_FLASH_SIZE + #define KNX_FLASH_SIZE 1024 + #pragma warning "KNX_FLASH_SIZE not defined, using 1024" + #endif +#endif + +#ifdef KNX_FLASH_CALLBACK + #ifndef KNX_FLASH_SIZE + #define KNX_FLASH_SIZE 0 + #endif + typedef uint32_t (*FlashCallbackSize)(); + typedef uint8_t* (*FlashCallbackRead)(); + typedef uint32_t (*FlashCallbackWrite)(uint32_t relativeAddress, uint8_t* buffer, size_t len); + typedef void (*FlashCallbackCommit)(); +#endif + +enum NvMemoryType +{ + Eeprom, + Flash, + Callback +}; + +class Platform +{ + public: + virtual ~Platform() {} + // ip config + virtual uint32_t currentIpAddress(); + virtual uint32_t currentSubnetMask(); + virtual uint32_t currentDefaultGateway(); + virtual void macAddress(uint8_t* data); + + // unique serial number + virtual uint32_t uniqueSerialNumber(); + + // basic stuff + virtual void restart() = 0; + virtual void fatalError() = 0; + + //multicast socket + virtual void setupMultiCast(uint32_t addr, uint16_t port); + virtual void closeMultiCast(); + virtual bool sendBytesMultiCast(uint8_t* buffer, uint16_t len); + virtual int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen); + virtual int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port); + + //unicast socket + virtual bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len); + + //UART + virtual void setupUart(); + virtual void closeUart(); + virtual int uartAvailable(); + virtual size_t writeUart(const uint8_t data); + virtual size_t writeUart(const uint8_t* buffer, size_t size); + virtual int readUart(); + virtual size_t readBytesUart(uint8_t* buffer, size_t length); + virtual bool overflowUart(); + virtual void flushUart(); + + // SPI + virtual void setupSpi(); + virtual void closeSpi(); + virtual int readWriteSpi(uint8_t* data, size_t len); + + //Memory + + // --- Overwrite these methods in the device-plattform to use the EEPROM Emulation API for UserMemory ---- + // + // --- changes to the UserMemory are written directly into the address space starting at getEepromBuffer + // --- commitToEeprom must save this to a non-volatile area if neccessary + virtual uint8_t* getEepromBuffer(uint32_t size); + virtual void commitToEeprom(); + // ------------------------------------------------------------------------------------------------------- + + virtual uint8_t* getNonVolatileMemoryStart(); + virtual size_t getNonVolatileMemorySize(); + virtual void commitNonVolatileMemory(); + // address is relative to start of nonvolatile memory + virtual uint32_t writeNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size); + virtual uint32_t readNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size); + virtual uint32_t writeNonVolatileMemory(uint32_t relativeAddress, uint8_t value, size_t repeat); + + NvMemoryType NonVolatileMemoryType(); + void NonVolatileMemoryType(NvMemoryType type); + + // --- Overwrite these methods in the device-plattform to use flash memory handling by the knx stack --- + // --- also set _memoryType = Flash in the device-plattform's contructor + // --- optional: overwrite writeBufferedEraseBlock() in the device-plattform to reduce overhead when flashing multiple pages + + // size of one flash page in bytes + virtual size_t flashPageSize(); + +#ifdef KNX_FLASH_CALLBACK + void registerFlashCallbacks( + FlashCallbackSize callbackFlashSize, + FlashCallbackRead callbackFlashRead, + FlashCallbackWrite callbackFlashWrite, + FlashCallbackCommit callbackFlashCommit); + + FlashCallbackSize callbackFlashSize(); + FlashCallbackRead callbackFlashRead(); + FlashCallbackWrite callbackFlashWrite(); + FlashCallbackCommit callbackFlashCommit(); +#endif + + protected: + // size of one EraseBlock in pages + virtual size_t flashEraseBlockSize(); + // start of user flash aligned to start of an erase block + virtual uint8_t* userFlashStart(); + // size of the user flash in EraseBlocks + virtual size_t userFlashSizeEraseBlocks(); + //relativ to userFlashStart + virtual void flashErase(uint16_t eraseBlockNum); + //write a single page to flash (pageNumber relative to userFashStart + virtual void flashWritePage(uint16_t pageNumber, uint8_t* data); + + + // ------------------------------------------------------------------------------------------------------- + + NvMemoryType _memoryType = Eeprom; + + void loadEraseblockContaining(uint32_t relativeAddress); + int32_t getEraseBlockNumberOf(uint32_t relativeAddress); + // writes _eraseblockBuffer to flash + virtual void writeBufferedEraseBlock(); + // copies a EraseBlock into the _eraseblockBuffer + void bufferEraseBlock(int32_t eraseBlockNumber); + + // in theory we would have to use this buffer for memory reads too, + // but because ets always restarts the device after programming it + // we can ignore this issue + uint8_t* _eraseblockBuffer = nullptr; + int32_t _bufferedEraseblockNumber = -1; + bool _bufferedEraseblockDirty = false; + +#ifdef KNX_FLASH_CALLBACK + FlashCallbackSize _callbackFlashSize = nullptr; + FlashCallbackRead _callbackFlashRead = nullptr; + FlashCallbackWrite _callbackFlashWrite = nullptr; + FlashCallbackCommit _callbackFlashCommit = nullptr; +#endif +}; diff --git a/components/knx/src/knx/property.cpp b/components/knx/src/knx/property.cpp new file mode 100644 index 0000000..55184df --- /dev/null +++ b/components/knx/src/knx/property.cpp @@ -0,0 +1,245 @@ +#include "property.h" +#include "bits.h" + +#include + +PropertyID Property::Id() const +{ + return _id; +} + +bool Property::WriteEnable() const +{ + return _writeEnable; +} + +PropertyDataType Property::Type() const +{ + return _type; +} + +uint16_t Property::MaxElements() const +{ + return _maxElements; +} + +uint8_t Property::Access() const +{ + return _access; +} + +uint8_t Property::ElementSize() const +{ + switch (_type) + { + case PDT_CHAR: + case PDT_CONTROL: // is actually 10 if written, but this is always handled with a callback + case PDT_GENERIC_01: + case PDT_UNSIGNED_CHAR: + case PDT_BITSET8: + case PDT_BINARY_INFORMATION: // only 1 bit really + case PDT_ENUM8: + case PDT_SCALING: + return 1; + + case PDT_GENERIC_02: + case PDT_INT: + case PDT_KNX_FLOAT: + case PDT_UNSIGNED_INT: + case PDT_VERSION: + case PDT_BITSET16: + return 2; + + case PDT_DATE: + case PDT_ESCAPE: + case PDT_FUNCTION: + case PDT_GENERIC_03: + case PDT_NE_FL: + case PDT_NE_VL: + case PDT_POLL_GROUP_SETTING: + case PDT_TIME: + case PDT_UTF8: + return 3; + + case PDT_FLOAT: + case PDT_GENERIC_04: + case PDT_LONG: + case PDT_UNSIGNED_LONG: + return 4; + + case PDT_GENERIC_05: + case PDT_SHORT_CHAR_BLOCK: + return 5; + + case PDT_GENERIC_06: + case PDT_ALARM_INFO: + return 6; + + case PDT_GENERIC_07: + return 7; + + case PDT_DATE_TIME: + case PDT_DOUBLE: + case PDT_GENERIC_08: + return 8; + + case PDT_GENERIC_09: + return 9; + + case PDT_CHAR_BLOCK: + case PDT_GENERIC_10: + return 10; + + case PDT_GENERIC_11: + return 11; + + case PDT_GENERIC_12: + return 12; + + case PDT_GENERIC_13: + return 13; + + case PDT_GENERIC_14: + return 14; + + case PDT_GENERIC_15: + return 15; + + case PDT_GENERIC_16: + return 16; + + case PDT_GENERIC_17: + return 17; + + case PDT_GENERIC_18: + return 18; + + case PDT_GENERIC_19: + return 19; + + case PDT_GENERIC_20: + return 20; + + default: + return 0; + } +} + +Property::Property(PropertyID id, bool writeEnable, PropertyDataType type, + uint16_t maxElements, uint8_t access) + : _id(id), _writeEnable(writeEnable), _type(type), _maxElements(maxElements), _access(access) +{} + +Property::~Property() +{} + + +uint8_t Property::read(uint8_t& value) const +{ + if (ElementSize() != 1) + return 0; + + return read(1, 1, &value); +} + + +uint8_t Property::read(uint16_t& value) const +{ + if (ElementSize() != 2) + return 0; + + uint8_t data[2]; + uint8_t count = read(1, 1, data); + + if (count > 0) + { + popWord(value, data); + } + + return count; +} + + +uint8_t Property::read(uint32_t& value) const +{ + if (ElementSize() != 4) + return 0; + + uint8_t data[4]; + uint8_t count = read(1, 1, data); + + if (count > 0) + { + popInt(value, data); + } + + return count; +} + +uint8_t Property::read(uint8_t* value) const +{ + return read(1, 1, value); +} + +uint8_t Property::write(uint8_t value) +{ + if (ElementSize() != 1) + return 0; + + return write(1, 1, &value); +} + + +uint8_t Property::write(uint16_t value) +{ + if (ElementSize() != 2) + return 0; + + uint8_t data[2]; + pushWord(value, data); + return write(1, 1, data); +} + + +uint8_t Property::write(uint32_t value) +{ + if (ElementSize() != 4) + return 0; + + uint8_t data[4]; + pushInt(value, data); + return write(1, 1, data); +} + + +uint8_t Property::write(const uint8_t* value) +{ + return write(1, 1, value); +} + + +uint8_t Property::write(uint16_t position, uint16_t value) +{ + if (ElementSize() != 2) + return 0; + + uint8_t data[2]; + pushWord(value, data); + return write(position, 1, data); +} + +void Property::command(uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) +{ + (void)data; + (void)length; + (void)resultData; + resultLength = 0; +} + +void Property::state(uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) +{ + (void)data; + (void)length; + (void)resultData; + resultLength = 0; +} diff --git a/components/knx/src/knx/property.h b/components/knx/src/knx/property.h new file mode 100644 index 0000000..816c89c --- /dev/null +++ b/components/knx/src/knx/property.h @@ -0,0 +1,292 @@ +/* + * property_types.h - BCU 2 property types of EIB objects. + * + * Copyright (c) 2014 Stefan Taferner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + */ +#pragma once + +#include +#include "save_restore.h" + +/** The data type of a property. */ +enum PropertyDataType +{ + PDT_CONTROL = 0x00, //!< length: 1 read, 10 write + PDT_CHAR = 0x01, //!< length: 1 + PDT_UNSIGNED_CHAR = 0x02, //!< length: 1 + PDT_INT = 0x03, //!< length: 2 + PDT_UNSIGNED_INT = 0x04, //!< length: 2 + PDT_KNX_FLOAT = 0x05, //!< length: 2 + PDT_DATE = 0x06, //!< length: 3 + PDT_TIME = 0x07, //!< length: 3 + PDT_LONG = 0x08, //!< length: 4 + PDT_UNSIGNED_LONG = 0x09, //!< length: 4 + PDT_FLOAT = 0x0a, //!< length: 4 + PDT_DOUBLE = 0x0b, //!< length: 8 + PDT_CHAR_BLOCK = 0x0c, //!< length: 10 + PDT_POLL_GROUP_SETTING = 0x0d, //!< length: 3 + PDT_SHORT_CHAR_BLOCK = 0x0e, //!< length: 5 + PDT_DATE_TIME = 0x0f, //!< length: 8 + PDT_VARIABLE_LENGTH = 0x10, + PDT_GENERIC_01 = 0x11, //!< length: 1 + PDT_GENERIC_02 = 0x12, //!< length: 2 + PDT_GENERIC_03 = 0x13, //!< length: 3 + PDT_GENERIC_04 = 0x14, //!< length: 4 + PDT_GENERIC_05 = 0x15, //!< length: 5 + PDT_GENERIC_06 = 0x16, //!< length: 6 + PDT_GENERIC_07 = 0x17, //!< length: 7 + PDT_GENERIC_08 = 0x18, //!< length: 8 + PDT_GENERIC_09 = 0x19, //!< length: 9 + PDT_GENERIC_10 = 0x1a, //!< length: 10 + PDT_GENERIC_11 = 0x1b, //!< length: 11 + PDT_GENERIC_12 = 0x1c, //!< length: 12 + PDT_GENERIC_13 = 0x1d, //!< length: 13 + PDT_GENERIC_14 = 0x1e, //!< length: 14 + PDT_GENERIC_15 = 0x1f, //!< length: 15 + PDT_GENERIC_16 = 0x20, //!< length: 16 + PDT_GENERIC_17 = 0x21, //!< length: 17 + PDT_GENERIC_18 = 0x22, //!< length: 18 + PDT_GENERIC_19 = 0x23, //!< length: 19 + PDT_GENERIC_20 = 0x24, //!< length: 20 + PDT_UTF8 = 0x2f, //!< length: 3 + PDT_VERSION = 0x30, //!< length: 3 + PDT_ALARM_INFO = 0x31, //!< length: 3 + PDT_BINARY_INFORMATION = 0x32, //!< length: 3 + PDT_BITSET8 = 0x33, //!< length: 3 + PDT_BITSET16 = 0x34, //!< length: 3 + PDT_ENUM8 = 0x35, //!< length: 3 + PDT_SCALING = 0x36, //!< length: 3 + PDT_NE_VL = 0x3c, //!< length: 3 + PDT_NE_FL = 0x3d, //!< length: 3 + PDT_FUNCTION = 0x3e, //!< length: 3 + PDT_ESCAPE = 0x3f, //!< length: 3 +}; + +enum PropertyID +{ + /** Interface Object Type independent Properties */ + PID_OBJECT_TYPE = 1, + PID_LOAD_STATE_CONTROL = 5, + PID_RUN_STATE_CONTROL = 6, + PID_TABLE_REFERENCE = 7, + PID_SERVICE_CONTROL = 8, + PID_FIRMWARE_REVISION = 9, + PID_SERIAL_NUMBER = 11, + PID_MANUFACTURER_ID = 12, + PID_PROG_VERSION = 13, + PID_DEVICE_CONTROL = 14, + PID_ORDER_INFO = 15, + PID_PEI_TYPE = 16, + PID_PORT_CONFIGURATION = 17, + PID_TABLE = 23, + PID_VERSION = 25, + PID_MCB_TABLE = 27, + PID_ERROR_CODE = 28, + PID_OBJECT_INDEX = 29, + PID_DOWNLOAD_COUNTER = 30, + + /** Properties in the Device Object */ + PID_ROUTING_COUNT = 51, + PID_PROG_MODE = 54, + PID_MAX_APDU_LENGTH = 56, + PID_SUBNET_ADDR = 57, + PID_DEVICE_ADDR = 58, + PID_IO_LIST = 71, + PID_HARDWARE_TYPE = 78, + PID_RF_DOMAIN_ADDRESS_CEMI_SERVER = 82, + PID_DEVICE_DESCRIPTOR = 83, + + /** Properties in the RF Medium Object */ + PID_RF_MULTI_TYPE = 51, + PID_RF_DOMAIN_ADDRESS = 56, + PID_RF_RETRANSMITTER = 57, + PID_RF_FILTERING_MODE_SUPPORT = 58, + PID_RF_FILTERING_MODE_SELECT = 59, + PID_RF_BIDIR_TIMEOUT = 60, + PID_RF_DIAG_SA_FILTER_TABLE = 61, + PID_RF_DIAG_BUDGET_TABLE = 62, + PID_RF_DIAG_PROBE = 63, + + /** KNXnet/IP Parameter Object */ + PID_PROJECT_INSTALLATION_ID = 51, + PID_KNX_INDIVIDUAL_ADDRESS = 52, + PID_ADDITIONAL_INDIVIDUAL_ADDRESSES = 53, + PID_CURRENT_IP_ASSIGNMENT_METHOD = 54, + PID_IP_ASSIGNMENT_METHOD = 55, + PID_IP_CAPABILITIES = 56, + PID_CURRENT_IP_ADDRESS = 57, + PID_CURRENT_SUBNET_MASK = 58, + PID_CURRENT_DEFAULT_GATEWAY = 59, + PID_IP_ADDRESS = 60, + PID_SUBNET_MASK = 61, + PID_DEFAULT_GATEWAY = 62, + PID_DHCP_BOOTP_SERVER = 63, + PID_MAC_ADDRESS = 64, + PID_SYSTEM_SETUP_MULTICAST_ADDRESS = 65, + PID_ROUTING_MULTICAST_ADDRESS = 66, + PID_TTL = 67, + PID_KNXNETIP_DEVICE_CAPABILITIES = 68, + PID_KNXNETIP_DEVICE_STATE = 69, + PID_KNXNETIP_ROUTING_CAPABILITIES = 70, + PID_PRIORITY_FIFO_ENABLED = 71, + PID_QUEUE_OVERFLOW_TO_IP = 72, + PID_QUEUE_OVERFLOW_TO_KNX = 73, + PID_MSG_TRANSMIT_TO_IP = 74, + PID_MSG_TRANSMIT_TO_KNX = 75, + PID_FRIENDLY_NAME = 76, + PID_ROUTING_BUSY_WAIT_TIME = 78, + PID_CUSTOM_RESERVED_TUNNELS_CTRL = 201, // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) + PID_CUSTOM_RESERVED_TUNNELS_IP = 202, // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) + + /** cEMI Server Object */ + PID_MEDIUM_TYPE = 51, + PID_COMM_MODE = 52, + PID_MEDIUM_AVAILABILITY = 53, + PID_ADD_INFO_TYPES = 54, + PID_TIME_BASE = 55, + PID_TRANSP_ENABLE = 56, + PID_CLIENT_SNA = 57, + PID_CLIENT_DEVICE_ADDRESS = 58, + PID_BIBAT_NEXTBLOCK = 59, + PID_RF_MODE_SELECT = 60, + PID_RF_MODE_SUPPORT = 61, + PID_RF_FILTERING_MODE_SELECT_CEMI_SERVER = 62, + PID_RF_FILTERING_MODE_SUPPORT_CEMI_SERVER = 63, + PID_COMM_MODES_SUPPORTED = 64, + PID_FILTERING_MODE_SUPPORT = 65, + PID_FILTERING_MODE_SELECT = 66, + PID_MAX_INTERFACE_APDU_LENGTH = 68, + PID_MAX_LOCAL_APDU_LENGTH = 69, + + /** Security Interface Object */ + PID_SECURITY_MODE = 51, // Enable and disable the Security Mode + PID_P2P_KEY_TABLE = 52, // Security keys used for securing point-to-point and broadcast communication + PID_GRP_KEY_TABLE = 53, // Security keys used for securing standard mode group communication + PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE = 54, // IAs and last valid sequence numbers of communication partners with secure links + PID_SECURITY_FAILURES_LOG = 55, // Provides security failure information + PID_TOOL_KEY = 56, // Stores the security information for the central MaC in S-Mode and Ctrl-Mode + PID_SECURITY_REPORT = 57, // KNX Data Security-related status and diagnostic information + PID_SECURITY_REPORT_CONTROL = 58, // Control the spontaneous communication of the security report through DMP_InterfaceObject-InfoReport_RCl + PID_SEQUENCE_NUMBER_SENDING = 59, // Sequence Number used for the next outgoing secure communication + PID_ZONE_KEY_TABLE = 60, // Security keys used for securing zone addressing communication + PID_GO_SECURITY_FLAGS = 61, // Defines the required security requirements for each group object + PID_ROLE_TABLE = 62, // Role table + PID_TOOL_SEQUENCE_NUMBER_SENDING = 250, // Sequence Number used for the next outgoing secure communication (Tool Access only, non-standardized!) + + /** Router Object */ + PID_MEDIUM_STATUS = 51, + PID_MAIN_LCCONFIG = 52, + PID_SUB_LCCONFIG = 53, + PID_MAIN_LCGRPCONFIG = 54, + PID_SUB_LCGRPCONFIG = 55, + PID_ROUTETABLE_CONTROL = 56, + PID_COUPLER_SERVICES_CONTROL = 57, + PID_MAX_APDU_LENGTH_ROUTER = 58, + PID_L2_COUPLER_TYPE = 59, // Only interesting for mask 0x0912 (TP1/TP1 coupler) + PID_HOP_COUNT = 61, // Only interesting in primary if other medium(secondary) is open medium without hopcount + PID_MEDIUM = 63, + PID_FILTER_TABLE_USE = 67, + PID_RF_ENABLE_SBC = 112, // Exists only if medium for this router object is RF (PDT_FUNCTION) + PID_IP_ENABLE_SBC = 120, // Exists only if medium for this router object is IP (PDT_FUNCTION) +}; + +enum LoadState +{ + LS_UNLOADED = 0, + LS_LOADED = 1, + LS_LOADING = 2, + LS_ERROR = 3, + LS_UNLOADING = 4, + LS_LOADCOMPLETING = 5 +}; + +enum LoadEvents +{ + LE_NOOP = 0, + LE_START_LOADING = 1, + LE_LOAD_COMPLETED = 2, + LE_ADDITIONAL_LOAD_CONTROLS = 3, + LE_UNLOAD = 4 +}; + +// 20.011 DPT_ErrorClass_System +enum ErrorCode +{ + E_NO_FAULT = 0, + E_GENERAL_DEVICE_FAULT = 1, + E_COMMUNICATION_FAULT = 2, + E_CONFIGURATION_FAULT = 3, + E_HARDWARE_FAULT = 4, + E_SOFTWARE_FAULT = 5, + E_INSUFFICIENT_NON_VOLATILE_MEMORY = 6, + E_INSUFFICIENT_VOLATILE_MEMORY = 7, + E_GOT_MEM_ALLOC_ZERO = 8, + E_CRC_ERROR = 9, + E_WATCHDOG_RESET = 10, + E_INVALID_OPCODE = 11, + E_GENERAL_PROTECTION_FAULT = 12, + E_MAX_TABLE_LENGTH_EXEEDED = 13, + E_GOT_UNDEF_LOAD_CMD = 14, + E_GAT_NOT_SORTED = 15, + E_INVALID_CONNECTION_NUMBER = 16, + E_INVALID_GO_NUMBER = 17, + E_GO_TYPE_TOO_BIG = 18 +}; + +/** The access level necessary to read a property of an interface object. */ +enum AccessLevel +{ + ReadLv0 = 0x00, + ReadLv1 = 0x10, + ReadLv2 = 0x20, + ReadLv3 = 0x30, + WriteLv0 = 0x00, + WriteLv1 = 0x01, + WriteLv2 = 0x02, + WriteLv3 = 0x03, +}; + +struct PropertyDescription +{ + PropertyID Id; + bool WriteEnable; + PropertyDataType Type; + uint16_t MaxElements; + uint8_t Access; +}; + +class Property : public SaveRestore +{ + public: + Property(PropertyID id, bool writeEnable, PropertyDataType type, uint16_t maxElements, uint8_t access); + virtual ~Property(); + PropertyID Id() const; + bool WriteEnable() const; + PropertyDataType Type() const; + uint16_t MaxElements() const; + uint8_t Access() const; + uint8_t ElementSize() const; + virtual uint8_t read(uint16_t start, uint8_t count, uint8_t* data) const = 0; + virtual uint8_t write(uint16_t start, uint8_t count, const uint8_t* data) = 0; + virtual void command(uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength); + virtual void state(uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength); + uint8_t read(uint8_t& value) const; + uint8_t read(uint16_t& value) const; + uint8_t read(uint32_t& value) const; + uint8_t read(uint8_t* value) const; + uint8_t write(uint8_t value); + uint8_t write(uint16_t value); + uint8_t write(uint16_t position, uint16_t value); + uint8_t write(uint32_t value); + uint8_t write(const uint8_t* value); + protected: + PropertyID _id; + bool _writeEnable; + PropertyDataType _type; + uint16_t _maxElements; + uint8_t _access; +}; diff --git a/components/knx/src/knx/rf_data_link_layer.cpp b/components/knx/src/knx/rf_data_link_layer.cpp new file mode 100644 index 0000000..17e1f05 --- /dev/null +++ b/components/knx/src/knx/rf_data_link_layer.cpp @@ -0,0 +1,385 @@ +#include "config.h" +#ifdef USE_RF + +#if defined(DeviceFamily_CC13X0) + #include "rf_physical_layer_cc1310.h" +#else + #include "rf_physical_layer_cc1101.h" +#endif +#include "rf_data_link_layer.h" + +#include "bits.h" +#include "platform.h" +#include "device_object.h" +#include "address_table_object.h" +#include "rf_medium_object.h" +#include "cemi_frame.h" + +#include +#include + +void RfDataLinkLayer::loop() +{ + if (!_enabled) + return; + + _rfPhy.loop(); +} + +bool RfDataLinkLayer::sendFrame(CemiFrame& frame) +{ + // If no serial number of domain address was set, + // use our own SN/DoA + if (frame.rfSerialOrDoA() == nullptr) + { + // Depending on this flag, use either KNX Serial Number + // or the RF domain address that was programmed by ETS + if (frame.systemBroadcast() == SysBroadcast) + { + frame.rfSerialOrDoA((uint8_t*)_deviceObject.propertyData(PID_SERIAL_NUMBER)); + } + else + { + frame.rfSerialOrDoA(_rfMediumObj.rfDomainAddress()); + } + } + + // If Link Layer frame is set to 0xFF, + // use our own counter + if (frame.rfLfn() == 0xFF) + { + // Set Data Link Layer Frame Number + frame.rfLfn(_frameNumber); + // Link Layer frame number counts 0..7 + _frameNumber = (_frameNumber + 1) & 0x7; + } + + // bidirectional device, battery is ok, signal strength indication is void (no measurement) + frame.rfInfo(0x02); + + if (!_enabled) + { + dataConReceived(frame, false); + return false; + } + + // TODO: Is queueing really required? + // According to the spec. the upper layer may only send a new L_Data.req if it received + // the L_Data.con for the previous L_Data.req. + addFrameTxQueue(frame); + + // TODO: For now L_data.req is confirmed immediately (L_Data.con) + // see 3.6.3 p.80: L_Data.con shall be generated AFTER transmission of the corresponsing frame + // RF sender will never generate L_Data.con with C=1 (Error), but only if the TX buffer overflows + // The RF sender cannot detect if the RF frame was transmitted successfully or not according to the spec. + dataConReceived(frame, true); + + return true; +} + +RfDataLinkLayer::RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, + NetworkLayerEntity& netLayerEntity, Platform& platform) + : DataLinkLayer(devObj, netLayerEntity, platform), + _rfMediumObj(rfMediumObj), + _rfPhy(*this, platform) +{ +} + +void RfDataLinkLayer::frameBytesReceived(uint8_t* rfPacketBuf, uint16_t length) +{ + // RF data link layer frame format + // See 3.2.5 p.22 + + // First block + smallest KNX telegram will give a minimum size of 22 bytes with checksum bytes + if (length < 21) + { + print("Received packet is too small. length: "); + println(length); + return; + } + +#if defined(DeviceFamily_CC13X0) + // Small optimization: + // We do not calculate the CRC16-DNP again for the first block. + // It was already done in the CC13x0 RX driver during reception. + // Also the two fixed bytes 0x44 and 0xFF are also there. + // So if we get here we can assume a valid block 1 +#else + // CRC16-DNP of first block is always located here + uint16_t block1Crc = rfPacketBuf[10] << 8 | rfPacketBuf[11]; + + // If the checksum was ok and the other + // two constant header bytes match the KNX-RF spec. (C-field: 0x44 and ESC-field: 0xFF)... + // then we seem to have a valid first block of an KNX RF frame. + // The first block basically contains the RF-info field and the KNX SN/Domain address. + if ((rfPacketBuf[1] == 0x44) && + (rfPacketBuf[2] == 0xFF) && + (crc16Dnp(rfPacketBuf, 10) == block1Crc)) +#endif + { + // bytes left from the remaining block(s) + uint16_t bytesLeft = length - 12; + // we use two pointers to move over the two buffers + uint8_t* pRfPacketBuf = &rfPacketBuf[12]; // pointer to start of RF frame block 2 (with CTRL field) + // Reserve 1 byte (+1) for the second ctrl field + // cEMI frame has two CTRL fields, but RF frame has only one, but uses ALWAYS extended frames + // Information for BOTH cEMI CTRL fields is distributed in a RF frame (RF CTRL field and RF L/NPCI field) + // So we cannot just copy an RF frame with CTRL fields as is + // KNX RF frame will be placed starting at cEMI CTRL2 field (so RF CTRL field is CTRL2 field cEMI) + uint8_t* pBuffer = &_buffer[CEMI_HEADER_SIZE + 1]; + // New length of the packet with CRC bytes removed, add space for CEMI header and the second CTRL field + uint16_t newLength = CEMI_HEADER_SIZE + 1; + + // Now check each block checksum and copy the payload of the block + // into a new buffer without checksum + uint16_t blockCrc; + bool crcOk = true; + + while (bytesLeft > 18) + { + // Get CRC16 from end of the block + blockCrc = pRfPacketBuf[16] << 8 | pRfPacketBuf[17]; + + if (crc16Dnp(pRfPacketBuf, 16) == blockCrc) + { + // Copy only the payload without the checksums + memcpy(pBuffer, pRfPacketBuf, 16); + } + else + { + crcOk = false; + break; + } + + pBuffer += 16; + pRfPacketBuf += 18; + newLength += 16; + bytesLeft -= 18; + } + + // Now process the last block + blockCrc = pRfPacketBuf[bytesLeft - 2] << 8 | pRfPacketBuf[bytesLeft - 1]; + crcOk = crcOk && (crc16Dnp(&pRfPacketBuf[0], bytesLeft - 2) == blockCrc); + + // If all checksums were ok, then... + if (crcOk) + { + // Copy rest of the received packet without checksum + memcpy(pBuffer, pRfPacketBuf, bytesLeft - 2); + newLength += bytesLeft - 2; + + // Prepare CEMI by writing/overwriting certain fields in the buffer (contiguous frame without CRC checksums) + // See 3.6.3 p.79: L_Data services for KNX RF asynchronous frames + // For now we do not use additional info, but use normal method arguments for CEMI + _buffer[0] = (uint8_t) L_data_ind; // L_data.ind + _buffer[1] = 0; // Additional info length (spec. says that local dev management is not required to use AddInfo internally) + _buffer[2] = 0; // CTRL1 field (will be set later, this is the field we reserved space for) + _buffer[3] &= 0x0F; // CTRL2 field (take only RFCtrl.b3..0, b7..4 shall always be 0 for asynchronous KNX RF) + + // Now get all control bits from the L/NPCI field of the RF frame + // so that we can overwrite it afterwards with the correct NPDU length + // Get data link layer frame number (LFN field) from L/NPCI.LFN (bit 3..1) + uint8_t lfn = (_buffer[8] & 0x0E) >> 1; + // Get address type from L/NPCI.LFN (bit 7) + AddressType addressType = (_buffer[8] & 0x80) ? GroupAddress : IndividualAddress; + // Get routing counter from L/NPCI.LFN (bit 6..4) and map to hop count in Ctrl2.b6-4 + uint8_t hopCount = (_buffer[8] & 0x70) >> 4; + // Get AddrExtensionType from L/NPCI.LFN (bit 7) and map to system broadcast flag in Ctrl1.b4 + SystemBroadcast systemBroadcast = (_buffer[8] & 0x01) ? Broadcast : SysBroadcast; + + // Setup L field of the cEMI frame with the NPDU length + // newLength -8 bytes (NPDU_LPDU_DIFF, no AddInfo) -1 byte length field -1 byte TPCI/APCI bits + _buffer[8] = newLength - NPDU_LPDU_DIFF - 1 - 1; + + // If we have a broadcast message (within the domain), + // then we received the domain address and not the KNX serial number + if (systemBroadcast == Broadcast) + { + // Check if the received RF domain address matches the one stored in the RF medium object + // If it does not match then skip the remaining processing + if (memcmp(_rfMediumObj.rfDomainAddress(), &rfPacketBuf[4], 6)) + { + println("RX domain address does not match. Skipping..."); + return; + } + } + + // TODO + // Frame duplication prevention based on LFN (see KKNX RF spec. 3.2.5 p.28) + + // Prepare the cEMI frame + CemiFrame frame(_buffer, newLength); + frame.frameType(ExtendedFrame); // KNX RF uses only extended frame format + frame.priority(SystemPriority); // Not used in KNX RF + frame.ack(AckDontCare); // Not used in KNX RF + frame.systemBroadcast(systemBroadcast); // Mapped from flag AddrExtensionType (KNX serial(0) or Domain Address(1)) + frame.hopCount(hopCount); // Hop count from routing counter + frame.addressType(addressType); // Group address or individual address + frame.rfSerialOrDoA(&rfPacketBuf[4]); // Copy pointer to field Serial or Domain Address (check broadcast flag what it is exactly) + frame.rfInfo(rfPacketBuf[3]); // RF-info field (1 byte) + frame.rfLfn(lfn); // Data link layer frame number (LFN field) + /* + print("RX LFN: "); + print(lfn); + print(" len: "); + print(newLength); + + print(" data: "); + printHex(" data: ", _buffer, newLength); + */ + frameReceived(frame); + } + } +} + +void RfDataLinkLayer::enabled(bool value) +{ + if (value && !_enabled) + { + if (_rfPhy.InitChip()) + { + _enabled = true; + print("ownaddr "); + println(_deviceObject.individualAddress(), HEX); + } + else + { + _enabled = false; + println("ERROR, RF transceiver not responding"); + } + + return; + } + + if (!value && _enabled) + { + _rfPhy.stopChip(); + _enabled = false; + return; + } +} + +bool RfDataLinkLayer::enabled() const +{ + return _enabled; +} + +DptMedium RfDataLinkLayer::mediumType() const +{ + return DptMedium::KNX_RF; +} + +void RfDataLinkLayer::fillRfFrame(CemiFrame& frame, uint8_t* data) +{ + uint16_t crc; + uint16_t length = frame.telegramLengthtRF(); + + data[0] = 9 + length; // Length block1 (always 9 bytes, without length itself) + Length of KNX telegram without CRCs + data[1] = 0x44; // C field: According to IEC870-5. KNX only uses SEND/NO REPLY (C = 44h) + data[2] = 0xFF; // ESC field: This field shall have the fixed value FFh. + data[3] = frame.rfInfo(); // RF-info field + + // Generate CRC16-DNP over the first block of data + pushByteArray(frame.rfSerialOrDoA(), 6, &data[4]); + crc = crc16Dnp(&data[0], 10); + pushWord(crc, &data[10]); + + // Put the complete KNX telegram into a temporary buffer + // as we have to add CRC16 checksums after each block of 16 bytes + frame.fillTelegramRF(_buffer); + + // Create a checksum for each block of full 16 bytes + uint16_t bytesLeft = length; + uint8_t* pBuffer = &_buffer[0]; + uint8_t* pData = &data[12]; + + while (bytesLeft > 16) + { + memcpy(pData, pBuffer, 16); + crc = crc16Dnp(pData, 16); + pushWord(crc, &pData[16]); + + pBuffer += 16; + pData += 18; + bytesLeft -= 16; + } + + // Copy remaining bytes of last block. Could be less than 16 bytes + memcpy(pData, pBuffer, bytesLeft); + // And add last CRC + crc = crc16Dnp(pData, bytesLeft); + pushWord(crc, &pData[bytesLeft]); +} + +void RfDataLinkLayer::addFrameTxQueue(CemiFrame& frame) +{ + _tx_queue_frame_t* tx_frame = new _tx_queue_frame_t; + + uint16_t length = frame.telegramLengthtRF(); // Just the pure KNX telegram from CTRL field until end of APDU + uint8_t nrFullBlocks = length / 16; // Number of full (16 bytes) RF blocks required + uint8_t bytesLeft = length % 16; // Remaining bytes of the last packet + + // Calculate total number of bytes required to store the complete raw RF frame + // Block1 always requires 12 bytes including Length and CRC + // Each full block has 16 bytes payload plus 2 bytes CRC + // Add remaining bytes of the last block and add 2 bytes for CRC + uint16_t totalLength = 12 + (nrFullBlocks * 18) + bytesLeft + 2; + + tx_frame->length = totalLength; + tx_frame->data = new uint8_t[tx_frame->length]; + tx_frame->next = NULL; + + // Prepare the raw RF frame + fillRfFrame(frame, tx_frame->data); + + /* + print("TX LFN: "); + print(frame.rfLfn()); + + print(" len: "); + print(totalLength); + + printHex(" data:", tx_frame->data, totalLength); + */ + if (_tx_queue.back == NULL) + { + _tx_queue.front = _tx_queue.back = tx_frame; + } + else + { + _tx_queue.back->next = tx_frame; + _tx_queue.back = tx_frame; + } +} + +bool RfDataLinkLayer::isTxQueueEmpty() +{ + if (_tx_queue.front == NULL) + { + return true; + } + + return false; +} + +void RfDataLinkLayer::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength) +{ + if (_tx_queue.front == NULL) + { + return; + } + + _tx_queue_frame_t* tx_frame = _tx_queue.front; + *sendBuffer = tx_frame->data; + *sendBufferLength = tx_frame->length; + _tx_queue.front = tx_frame->next; + + if (_tx_queue.front == NULL) + { + _tx_queue.back = NULL; + } + + delete tx_frame; +} + +#endif diff --git a/components/knx/src/knx/rf_data_link_layer.h b/components/knx/src/knx/rf_data_link_layer.h new file mode 100644 index 0000000..4763536 --- /dev/null +++ b/components/knx/src/knx/rf_data_link_layer.h @@ -0,0 +1,67 @@ +#pragma once + +#include "config.h" +#ifdef USE_RF + +#include +#include "data_link_layer.h" + +#define MAX_KNX_TELEGRAM_SIZE 263 + +class RfMediumObject; + +class RfDataLinkLayer : public DataLinkLayer +{ +#if defined(DeviceFamily_CC13X0) + friend class RfPhysicalLayerCC1310; +#else + friend class RfPhysicalLayerCC1101; +#endif + using DataLinkLayer::_deviceObject; + using DataLinkLayer::_platform; + + public: + RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, NetworkLayerEntity& netLayerEntity, + Platform& platform); + + void loop(); + void enabled(bool value); + bool enabled() const; + DptMedium mediumType() const override; + + private: + bool _enabled = false; + uint8_t _loopState = 0; + + uint8_t _buffer[512]; + + uint8_t _frameNumber = 0; + + struct _tx_queue_frame_t + { + uint8_t* data; + uint16_t length; + _tx_queue_frame_t* next; + }; + + struct _tx_queue_t + { + _tx_queue_frame_t* front = NULL; + _tx_queue_frame_t* back = NULL; + } _tx_queue; + + RfMediumObject& _rfMediumObj; +#if defined(DeviceFamily_CC13X0) + RfPhysicalLayerCC1310 _rfPhy; +#else + RfPhysicalLayerCC1101 _rfPhy; +#endif + void fillRfFrame(CemiFrame& frame, uint8_t* data); + void addFrameTxQueue(CemiFrame& frame); + bool isTxQueueEmpty(); + void loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength); + bool sendFrame(CemiFrame& frame); + void frameBytesReceived(uint8_t* buffer, uint16_t length); +}; + +#endif diff --git a/components/knx/src/knx/rf_medium_object.cpp b/components/knx/src/knx/rf_medium_object.cpp new file mode 100644 index 0000000..8ccebd9 --- /dev/null +++ b/components/knx/src/knx/rf_medium_object.cpp @@ -0,0 +1,60 @@ +#include "config.h" +#ifdef USE_RF + +#include +#include "rf_medium_object.h" +#include "bits.h" +#include "data_property.h" +#include "function_property.h" + +RfMediumObject::RfMediumObject() +{ + uint8_t rfDomainAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // see KNX RF S-Mode AN160 p.11 + Property* properties[] = + { + new DataProperty(PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_RF_MEDIUM), + new DataProperty(PID_RF_MULTI_TYPE, true, PDT_GENERIC_01, 1, ReadLv3 | WriteLv2, (uint8_t)0x00), + new DataProperty(PID_RF_RETRANSMITTER, false, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0, (uint8_t)0x00), + new DataProperty(PID_RF_DOMAIN_ADDRESS, true, PDT_GENERIC_06, 1, ReadLv3 | WriteLv2, rfDomainAddress), + new FunctionProperty(this, PID_RF_BIDIR_TIMEOUT, + [](RfMediumObject * io, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) + { + resultData[0] = 0x00; // success + resultData[1] = 0xFF; // permanent bidirectional device + resultData[2] = 0xFF; // permanent bidirectional device + resultLength = 3; + }, + [](RfMediumObject * io, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) + { + resultData[0] = 0x00; // success + resultData[1] = 0xFF; // permanent bidirectional device + resultData[2] = 0xFF; // permanent bidirectional device + resultLength = 3; + }), + /* These properties are used in NMP_LinkBudget_Measure to diagnose the Link Budget of the communication. + This in not implemented yet. + new DataProperty(PID_RF_DIAG_SA_FILTER_TABLE, true, PDT_GENERIC_03, 8, ReadLv3 | WriteLv3), + new DataProperty(PID_RF_DIAG_BUDGET_TABLE, false, PDT_GENERIC_03, 8, ReadLv3 | WriteLv0), + new FunctionProperty(this, PID_RF_DIAG_PROBE, + [](RfMediumObject* io, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) + { + }, + [](RfMediumObject* io, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) + { + }), */ + }; + initializeProperties(sizeof(properties), properties); +} + +const uint8_t* RfMediumObject::rfDomainAddress() +{ + DataProperty* prop = (DataProperty*)property(PID_RF_DOMAIN_ADDRESS); + return prop->data(); +} + +void RfMediumObject::rfDomainAddress(const uint8_t* value) +{ + Property* prop = property(PID_RF_DOMAIN_ADDRESS); + prop->write(value); +} +#endif diff --git a/components/knx/src/knx/rf_medium_object.h b/components/knx/src/knx/rf_medium_object.h new file mode 100644 index 0000000..3e03b53 --- /dev/null +++ b/components/knx/src/knx/rf_medium_object.h @@ -0,0 +1,19 @@ +#pragma once + +#include "config.h" +#ifdef USE_RF + +#include "interface_object.h" + +class RfMediumObject: public InterfaceObject +{ + public: + RfMediumObject(); + const uint8_t* rfDomainAddress(); + void rfDomainAddress(const uint8_t* value); + + private: + uint8_t _rfDiagSourceAddressFilterTable[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + uint8_t _rfDiagLinkBudgetTable[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; +}; +#endif diff --git a/components/knx/src/knx/rf_physical_layer.h b/components/knx/src/knx/rf_physical_layer.h new file mode 100644 index 0000000..f9b6389 --- /dev/null +++ b/components/knx/src/knx/rf_physical_layer.h @@ -0,0 +1,30 @@ +#pragma once + +#include "config.h" +#ifdef USE_RF + +#include + +#include "platform.h" + +// Calculate the real packet size out of the L-field of FT3 frame data. See KNX-RF spec. 3.2.5 Data Link Layer frame format +#define PACKET_SIZE(lField) ((((lField - 10 /*size of first pkt*/))/16 + 2 /*CRC in first pkt */) * 2 /*to bytes*/ +lField + 1 /*size of len byte*/) + +class RfDataLinkLayer; + +class RfPhysicalLayer +{ + public: + RfPhysicalLayer(RfDataLinkLayer& rfDataLinkLayer, Platform& platform) + : _rfDataLinkLayer(rfDataLinkLayer), _platform(platform) {} + + virtual bool InitChip() = 0; + virtual void stopChip() = 0; + virtual void loop() = 0; + + protected: + RfDataLinkLayer& _rfDataLinkLayer; + Platform& _platform; +}; + +#endif diff --git a/components/knx/src/knx/rf_physical_layer_cc1101.cpp b/components/knx/src/knx/rf_physical_layer_cc1101.cpp new file mode 100644 index 0000000..daa96aa --- /dev/null +++ b/components/knx/src/knx/rf_physical_layer_cc1101.cpp @@ -0,0 +1,784 @@ +#ifndef DeviceFamily_CC13X0 + +#include "config.h" +#ifdef USE_RF + +#include "rf_physical_layer_cc1101.h" +#include "rf_data_link_layer.h" + +#include "bits.h" +#include "platform.h" + +#include +#include + +// Table for encoding 4-bit data into a 8-bit Manchester encoding. +const uint8_t RfPhysicalLayerCC1101::manchEncodeTab[16] = {0xAA, // 0x0 Manchester encoded + 0xA9, // 0x1 Manchester encoded + 0xA6, // 0x2 Manchester encoded + 0xA5, // 0x3 Manchester encoded + 0x9A, // 0x4 Manchester encoded + 0x99, // 0x5 Manchester encoded + 0x96, // 0x6 Manchester encoded + 0x95, // 0x7 Manchester encoded + 0x6A, // 0x8 Manchester encoded + 0x69, // 0x9 Manchester encoded + 0x66, // 0xA Manchester encoded + 0x65, // 0xB Manchester encoded + 0x5A, // 0xC Manchester encoded + 0x59, // 0xD Manchester encoded + 0x56, // 0xE Manchester encoded + 0x55 + }; // 0xF Manchester encoded + +// Table for decoding 4-bit Manchester encoded data into 2-bit +// data. 0xFF indicates invalid Manchester encoding +const uint8_t RfPhysicalLayerCC1101::manchDecodeTab[16] = {0xFF, // Manchester encoded 0x0 decoded + 0xFF, // Manchester encoded 0x1 decoded + 0xFF, // Manchester encoded 0x2 decoded + 0xFF, // Manchester encoded 0x3 decoded + 0xFF, // Manchester encoded 0x4 decoded + 0x03, // Manchester encoded 0x5 decoded + 0x02, // Manchester encoded 0x6 decoded + 0xFF, // Manchester encoded 0x7 decoded + 0xFF, // Manchester encoded 0x8 decoded + 0x01, // Manchester encoded 0x9 decoded + 0x00, // Manchester encoded 0xA decoded + 0xFF, // Manchester encoded 0xB decoded + 0xFF, // Manchester encoded 0xC decoded + 0xFF, // Manchester encoded 0xD decoded + 0xFF, // Manchester encoded 0xE decoded + 0xFF + };// Manchester encoded 0xF decoded + +// Product = CC1101 +// Chip version = A (VERSION = 0x04) +// Crystal accuracy = 10 ppm +// X-tal frequency = 26 MHz +// RF output power = + 10 dBm +// RX filterbandwidth = 270 kHz +// Deviation = 47 kHz +// Datarate = 32.73 kBaud +// Modulation = (0) 2-FSK +// Manchester enable = (0) Manchester disabled +// RF Frequency = 868.299866 MHz +// Channel spacing = 199.951172 kHz +// Channel number = 0 +// Optimization = - +// Sync mode = (5) 15/16 + carrier-sense above threshold +// Format of RX/TX data = (0) Normal mode, use FIFOs for RX and TX +// CRC operation = (0) CRC disabled for TX and RX +// Forward Error Correction = (0) FEC disabled +// Length configuration = (0) Fixed length packets, length configured in PKTLEN register. +// Packetlength = 255 +// Preamble count = (2) 4 bytes +// Append status = 0 +// Address check = (0) No address check +// FIFO autoflush = 0 +// Device address = 0 +// GDO0 signal selection = ( 6) Asserts when sync word has been sent / received, and de-asserts at the end of the packet +// GDO2 signal selection = ( 0) Asserts when RX FiFO threshold +const uint8_t RfPhysicalLayerCC1101::cc1101_2FSK_32_7_kb[CFG_REGISTER] = +{ + 0x00, // IOCFG2 GDO2 Output Pin Configuration + 0x2E, // IOCFG1 GDO1 Output Pin Configuration + 0x06, // IOCFG0 GDO0 Output Pin Configuration + 0x40, // FIFOTHR RX FIFO and TX FIFO Thresholds // 4 bytes in RX FIFO (2 bytes manchester encoded) + 0x76, // SYNC1 Sync Word + 0x96, // SYNC0 Sync Word + 0xFF, // PKTLEN Packet Length + 0x00, // PKTCTRL1 Packet Automation Control + 0x00, // PKTCTRL0 Packet Automation Control + 0x00, // ADDR Device Address + 0x00, // CHANNR Channel Number + 0x08, // FSCTRL1 Frequency Synthesizer Control + 0x00, // FSCTRL0 Frequency Synthesizer Control + 0x21, // FREQ2 Frequency Control Word + 0x65, // FREQ1 Frequency Control Word + 0x6A, // FREQ0 Frequency Control Word + 0x6A, // MDMCFG4 Modem Configuration + 0x4A, // MDMCFG3 Modem Configuration + 0x05, // MDMCFG2 Modem Configuration + 0x22, // MDMCFG1 Modem Configuration + 0xF8, // MDMCFG0 Modem Configuration + 0x47, // DEVIATN Modem Deviation Setting + 0x07, // MCSM2 Main Radio Control State Machine Configuration + 0x30, // MCSM1 Main Radio Control State Machine Configuration (IDLE after TX and RX) + 0x18, // MCSM0 Main Radio Control State Machine Configuration + 0x2E, // FOCCFG Frequency Offset Compensation Configuration + 0x6D, // BSCFG Bit Synchronization Configuration + 0x43, // AGCCTRL2 AGC Control 0x04, // AGCCTRL2 magn target 33dB vs 36dB (max LNA+LNA2 gain vs. ) (highest gain cannot be used vs. all gain settings) + 0x40, // AGCCTRL1 AGC Control 0x09, // AGCCTRL1 carrier sense threshold disabled vs. 7dB below magn target (LNA prio strat. 1 vs 0) + 0x91, // AGCCTRL0 AGC Control 0xB2, // AGCCTRL0 channel filter samples 16 vs.32 + 0x87, // WOREVT1 High Byte Event0 Timeout + 0x6B, // WOREVT0 Low Byte Event0 Timeout + 0xFB, // WORCTRL Wake On Radio Control + 0xB6, // FREND1 Front End RX Configuration + 0x10, // FREND0 Front End TX Configuration + 0xE9, // FSCAL3 Frequency Synthesizer Calibration 0xEA, // FSCAL3 + 0x2A, // FSCAL2 Frequency Synthesizer Calibration + 0x00, // FSCAL1 Frequency Synthesizer Calibration + 0x1F, // FSCAL0 Frequency Synthesizer Calibration + 0x41, // RCCTRL1 RC Oscillator Configuration + 0x00, // RCCTRL0 RC Oscillator Configuration + 0x59, // FSTEST Frequency Synthesizer Calibration Control + 0x7F, // PTEST Production Test + 0x3F, // AGCTEST AGC Test + 0x81, // TEST2 Various Test Settings + 0x35, // TEST1 Various Test Settings + 0x09 // TEST0 Various Test Settings +}; + +//Patable index: -30 -20- -15 -10 0 5 7 10 dBm +const uint8_t RfPhysicalLayerCC1101::paTablePower868[8] = {0x03, 0x17, 0x1D, 0x26, 0x50, 0x86, 0xCD, 0xC0}; + +RfPhysicalLayerCC1101::RfPhysicalLayerCC1101(RfDataLinkLayer& rfDataLinkLayer, Platform& platform) + : RfPhysicalLayer(rfDataLinkLayer, platform) +{ +} + +void RfPhysicalLayerCC1101::manchEncode(uint8_t* uncodedData, uint8_t* encodedData) +{ + uint8_t data0, data1; + + // - Shift to get 4-bit data values + data1 = (((*uncodedData) >> 4) & 0x0F); + data0 = ((*uncodedData) & 0x0F); + + // - Perform Manchester encoding - + *encodedData = (manchEncodeTab[data1]); + *(encodedData + 1) = manchEncodeTab[data0]; +} + +bool RfPhysicalLayerCC1101::manchDecode(uint8_t* encodedData, uint8_t* decodedData) +{ + uint8_t data0, data1, data2, data3; + + // - Shift to get 4 bit data and decode + data3 = ((*encodedData >> 4) & 0x0F); + data2 = ( *encodedData & 0x0F); + data1 = ((*(encodedData + 1) >> 4) & 0x0F); + data0 = ((*(encodedData + 1)) & 0x0F); + + // Check for invalid Manchester encoding + if ( (manchDecodeTab[data3] == 0xFF ) | (manchDecodeTab[data2] == 0xFF ) | + (manchDecodeTab[data1] == 0xFF ) | (manchDecodeTab[data0] == 0xFF ) ) + { + return false; + } + + // Shift result into a byte + *decodedData = (manchDecodeTab[data3] << 6) | (manchDecodeTab[data2] << 4) | + (manchDecodeTab[data1] << 2) | manchDecodeTab[data0]; + + return true; +} + +uint8_t RfPhysicalLayerCC1101::sIdle() +{ + uint8_t marcState; + uint32_t timeStart; + + spiWriteStrobe(SIDLE); //sets to idle first. must be in + + marcState = 0xFF; //set unknown/dummy state value + timeStart = millis(); + + while ((marcState != MARCSTATE_IDLE) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x01 = sidle + { + marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX + } + + //print("marcstate: 0x"); + //println(marcState, HEX); + + if (marcState != MARCSTATE_IDLE) + { + println("Timeout when trying to set idle state."); + return false; + } + + return true; +} + +uint8_t RfPhysicalLayerCC1101::sReceive() +{ + uint8_t marcState; + uint32_t timeStart; + + spiWriteStrobe(SRX); //writes receive strobe (receive mode) + + marcState = 0xFF; //set unknown/dummy state value + timeStart = millis(); + + while ((marcState != MARCSTATE_RX) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x0D = RX + { + marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX + } + + //print("marcstate: 0x"); + //println(marcState, HEX); + + if (marcState != MARCSTATE_RX) + { + println("Timeout when trying to set receive state."); + return false; + } + + return true; +} + +void RfPhysicalLayerCC1101::spiWriteRegister(uint8_t spi_instr, uint8_t value) +{ + uint8_t tbuf[2] = {0}; + tbuf[0] = spi_instr | WRITE_SINGLE_BYTE; + tbuf[1] = value; + uint8_t len = 2; + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(tbuf, len); + digitalWrite(SPI_SS_PIN, HIGH); +} + +uint8_t RfPhysicalLayerCC1101::spiReadRegister(uint8_t spi_instr) +{ + uint8_t value; + uint8_t rbuf[2] = {0}; + rbuf[0] = spi_instr | READ_SINGLE_BYTE; + uint8_t len = 2; + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(rbuf, len); + digitalWrite(SPI_SS_PIN, HIGH); + value = rbuf[1]; + //printf("SPI_arr_0: 0x%02X\n", rbuf[0]); + //printf("SPI_arr_1: 0x%02X\n", rbuf[1]); + return value; +} + +uint8_t RfPhysicalLayerCC1101::spiWriteStrobe(uint8_t spi_instr) +{ + uint8_t tbuf[1] = {0}; + tbuf[0] = spi_instr; + //printf("SPI_data: 0x%02X\n", tbuf[0]); + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(tbuf, 1); + digitalWrite(SPI_SS_PIN, HIGH); + return tbuf[0]; +} + +void RfPhysicalLayerCC1101::spiReadBurst(uint8_t spi_instr, uint8_t* pArr, uint8_t len) +{ + uint8_t rbuf[len + 1]; + rbuf[0] = spi_instr | READ_BURST; + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(rbuf, len + 1); + digitalWrite(SPI_SS_PIN, HIGH); + + for (uint8_t i = 0; i < len ; i++ ) + { + pArr[i] = rbuf[i + 1]; + //printf("SPI_arr_read: 0x%02X\n", pArr[i]); + } +} + +void RfPhysicalLayerCC1101::spiWriteBurst(uint8_t spi_instr, const uint8_t* pArr, uint8_t len) +{ + uint8_t tbuf[len + 1]; + tbuf[0] = spi_instr | WRITE_BURST; + + for (uint8_t i = 0; i < len ; i++ ) + { + tbuf[i + 1] = pArr[i]; + //printf("SPI_arr_write: 0x%02X\n", tbuf[i+1]); + } + + digitalWrite(SPI_SS_PIN, LOW); + _platform.readWriteSpi(tbuf, len + 1); + digitalWrite(SPI_SS_PIN, HIGH); +} + +void RfPhysicalLayerCC1101::powerDownCC1101() +{ + // Set IDLE state first + sIdle(); + delayMicroseconds(100); + // CC1101 Power Down + spiWriteStrobe(SPWD); +} + +void RfPhysicalLayerCC1101::setOutputPowerLevel(int8_t dBm) +{ + uint8_t pa = 0xC0; + + if (dBm <= -30) + pa = 0x00; + else if (dBm <= -20) + pa = 0x01; + else if (dBm <= -15) + pa = 0x02; + else if (dBm <= -10) + pa = 0x03; + else if (dBm <= 0) + pa = 0x04; + else if (dBm <= 5) + pa = 0x05; + else if (dBm <= 7) + pa = 0x06; + else if (dBm <= 10) + pa = 0x07; + + spiWriteRegister(FREND0, pa); +} + +bool RfPhysicalLayerCC1101::InitChip() +{ + // Setup SPI and GPIOs + _platform.setupSpi(); + pinMode(GPIO_GDO2_PIN, INPUT); + pinMode(GPIO_GDO0_PIN, INPUT); + pinMode(SPI_SS_PIN, OUTPUT); + + // Toggle chip select signal as described in CC11xx manual + digitalWrite(SPI_SS_PIN, HIGH); + delayMicroseconds(30); + digitalWrite(SPI_SS_PIN, LOW); + delayMicroseconds(30); + digitalWrite(SPI_SS_PIN, HIGH); + delayMicroseconds(45); + + // Send SRES command + digitalWrite(SPI_SS_PIN, LOW); + delay(10); // Normally we would have to poll MISO here: while(_platform.readGpio(SPI_MISO_PIN)); + spiWriteStrobe(SRES); + // Wait for chip to finish internal reset + delay(10); // Normally we would have to poll MISO here: while(_platform.readGpio(SPI_MISO_PIN)); + digitalWrite(SPI_SS_PIN, HIGH); + + // Flush the FIFOs + spiWriteStrobe(SFTX); + delayMicroseconds(100); + spiWriteStrobe(SFRX); + delayMicroseconds(100); + + uint8_t partnum = spiReadRegister(PARTNUM); //reads CC1101 partnumber; + uint8_t version = spiReadRegister(VERSION); //reads CC1101 version number; + + // Checks if valid chip ID is found. Usually 0x03 or 0x14. if not -> abort + if (version == 0x00 || version == 0xFF) + { + println("No CC11xx found!"); + stopChip(); + return false; + } + + print("Partnumber: 0x"); + println(partnum, HEX); + print("Version : 0x"); + println(version, HEX); + + // Set modulation mode 2FSK, 32768kbit/s + spiWriteBurst(WRITE_BURST, cc1101_2FSK_32_7_kb, CFG_REGISTER); + + // Set PA table + spiWriteBurst(PATABLE_BURST, paTablePower868, 8); + + // Set ISM band to 868.3MHz + spiWriteRegister(FREQ2, 0x21); + spiWriteRegister(FREQ1, 0x65); + spiWriteRegister(FREQ0, 0x6A); + + // Set channel 0 in ISM band + spiWriteRegister(CHANNR, 0); + + // Set PA to 0dBm as default + setOutputPowerLevel(0); + + return true; +} + +void RfPhysicalLayerCC1101::stopChip() +{ + powerDownCC1101(); + + _platform.closeSpi(); +} + +void RfPhysicalLayerCC1101::showRegisterSettings() +{ + uint8_t config_reg_verify[CFG_REGISTER]; + uint8_t Patable_verify[CFG_REGISTER]; + + spiReadBurst(READ_BURST, config_reg_verify, CFG_REGISTER); //reads all 47 config register from cc1101 + spiReadBurst(PATABLE_BURST, Patable_verify, 8); //reads output power settings from cc1101 + + println("Config Register:"); + printHex("", config_reg_verify, CFG_REGISTER); + + println("PaTable:"); + printHex("", Patable_verify, 8); +} + +void RfPhysicalLayerCC1101::loop() +{ + switch (_loopState) + { + case TX_START: + { + prevStatusGDO0 = 0; + prevStatusGDO2 = 0; + // Set sync word in TX mode + // The same sync word is used in RX mode, but we use it in different way here: + // Important: the TX FIFO must provide the last byte of the + // sync word + spiWriteRegister(SYNC1, 0x54); + spiWriteRegister(SYNC0, 0x76); + // Set TX FIFO threshold to 33 bytes + spiWriteRegister(FIFOTHR, 0x47); + // Set GDO2 to be TX FIFO threshold signal + spiWriteRegister(IOCFG2, 0x02); + // Set GDO0 to be packet transmitted signal + spiWriteRegister(IOCFG0, 0x06); + // Flush TX FIFO + spiWriteStrobe(SFTX); + + _rfDataLinkLayer.loadNextTxFrame(&sendBuffer, &sendBufferLength); + + // Calculate total number of bytes in the KNX RF packet from L-field + pktLen = PACKET_SIZE(sendBuffer[0]); + + // Check for valid length + if ((pktLen == 0) || (pktLen > 290)) + { + println("TX packet length error!"); + break; + } + + // Manchester encoded data takes twice the space plus + // 1 byte for postamble and 1 byte (LSB) of the synchronization word + bytesLeft = (2 * pktLen) + 2; + // Last byte of synchronization word + buffer[0] = 0x96; + + // Manchester encode packet + for (int i = 0; i < pktLen; i++) + { + manchEncode(&sendBuffer[i], &buffer[1 + i * 2]); + } + + // Append the postamble sequence + buffer[1 + bytesLeft - 1] = 0x55; + + // Fill TX FIFO + pByteIndex = &buffer[0]; + + // Set fixed packet length mode if less than 256 bytes to transmit + if (bytesLeft < 256) + { + spiWriteRegister(PKTLEN, bytesLeft); + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + else // Else set infinite length mode + { + uint8_t fixedLength = bytesLeft % 256; + spiWriteRegister(PKTLEN, fixedLength); + spiWriteRegister(PKTCTRL0, 0x02); + fixedLengthMode = false; + } + + uint8_t bytesToWrite = MIN(64, bytesLeft); + spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite); + pByteIndex += bytesToWrite; + bytesLeft -= bytesToWrite; + + // Enable transmission of packet + spiWriteStrobe(STX); + + _loopState = TX_ACTIVE; + } + + // Fall through + + case TX_ACTIVE: + { + // Check if we have an incomplete packet transmission + if (syncStart && (millis() - packetStartTime > TX_PACKET_TIMEOUT)) + { + println("TX packet timeout!"); + // Set transceiver to IDLE (no RX or TX) + sIdle(); + _loopState = TX_END; + break; + } + + // Detect falling edge 1->0 on GDO2 + statusGDO2 = digitalRead(GPIO_GDO2_PIN); + + if (prevStatusGDO2 != statusGDO2) + { + prevStatusGDO2 = statusGDO2; + + // Check if signal GDO2 is de-asserted (TX FIFO is below threshold of 33 bytes, i.e. TX FIFO is half full) + if (statusGDO2 == 0) + { + // - TX FIFO half full detected (< 33 bytes) + // Write data fragment to TX FIFO + uint8_t bytesToWrite = MIN(64, bytesLeft); + spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite); + pByteIndex += bytesToWrite; + bytesLeft -= bytesToWrite; + + // Set fixed length mode if less than 256 left to transmit + if ( (bytesLeft < (256 - 64)) && !fixedLengthMode ) + { + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + } + } + + // Detect falling edge 1->0 on GDO0 + statusGDO0 = digitalRead(GPIO_GDO0_PIN); + + if (prevStatusGDO0 != statusGDO0) + { + prevStatusGDO0 = statusGDO0; + + // If GDO0 is de-asserted: TX packet complete or TX FIFO underflow + if (statusGDO0 == 0x00) + { + // There might be an TX FIFO underflow + uint8_t chipStatusBytes = spiWriteStrobe(SNOP); + + if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_TX_UNDERFLOW) + { + println("TX FIFO underflow!"); + // Set transceiver to IDLE (no RX or TX) + sIdle(); + } + + _loopState = TX_END; + } + else + { + // GDO0 asserted because sync word was transmitted + //println("TX Syncword!"); + // wait for TX_PACKET_TIMEOUT milliseconds + // Complete packet must have been transmitted within this time + packetStartTime = millis(); + syncStart = true; + } + } + } + break; + + case TX_END: + { + // free buffer + delete sendBuffer; + // Go back to RX after TX + _loopState = RX_START; + } + break; + + case RX_START: + { + prevStatusGDO2 = 0; + prevStatusGDO0 = 0; + syncStart = false; + packetStart = true; + fixedLengthMode = false; + pByteIndex = buffer; + bytesLeft = 0; + pktLen = 0; + // Set sync word in RX mode + // The same sync word is used in TX mode, but we use it in different way + spiWriteRegister(SYNC1, 0x76); + spiWriteRegister(SYNC0, 0x96); + // Set GDO2 to be RX FIFO threshold signal + spiWriteRegister(IOCFG2, 0x00); + // Set GDO0 to be packet received signal + spiWriteRegister(IOCFG0, 0x06); + // Set RX FIFO threshold to 4 bytes + spiWriteRegister(FIFOTHR, 0x40); + // Set infinite pktlen mode + spiWriteRegister(PKTCTRL0, 0x02); + // Flush RX FIFO + spiWriteStrobe(SFRX); + // Start RX + sReceive(); + _loopState = RX_ACTIVE; + } + break; + + case RX_ACTIVE: + { + if (!_rfDataLinkLayer.isTxQueueEmpty() && !syncStart) + { + sIdle(); + _loopState = TX_START; + break; + } + + // Check if we have an incomplete packet reception + // This is related to CC1101 errata "Radio stays in RX state instead of entering RXFIFO_OVERFLOW state" + if (syncStart && (millis() - packetStartTime > RX_PACKET_TIMEOUT)) + { + println("RX packet timeout!"); + //uint8_t marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX + //print("marcstate: 0x"); + //println(marcState, HEX); + sIdle(); + _loopState = RX_START; + break; + } + + // Detect rising edge 0->1 on GDO2 + statusGDO2 = digitalRead(GPIO_GDO2_PIN); + + if (prevStatusGDO2 != statusGDO2) + { + prevStatusGDO2 = statusGDO2; + + // Check if signal GDO2 is asserted (RX FIFO is equal to or above threshold of 4 bytes) + if (statusGDO2 == 1) + { + if (packetStart) + { + // - RX FIFO 4 bytes detected - + // Calculate the total length of the packet, and set fixed mode if less + // than 255 bytes to receive + + // Read the 2 first bytes + spiReadBurst(RXFIFO_BURST, pByteIndex, 2); + + // Decode the L-field + if (!manchDecode(&buffer[0], &packet[0])) + { + //println("Could not decode L-field: manchester code violation"); + _loopState = RX_START; + break; + } + + // Get bytes to receive from L-field, multiply by 2 because of manchester code + pktLen = 2 * PACKET_SIZE(packet[0]); + + // - Length mode - + if (pktLen < 256) + { + // Set fixed packet length mode is less than 256 bytes + spiWriteRegister(PKTLEN, pktLen); + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + else + { + // Infinite packet length mode is more than 255 bytes + // Calculate the PKTLEN value + uint8_t fixedLength = pktLen % 256; + spiWriteRegister(PKTLEN, fixedLength); + } + + pByteIndex += 2; + bytesLeft = pktLen - 2; + + // Set RX FIFO threshold to 32 bytes + packetStart = false; + spiWriteRegister(FIFOTHR, 0x47); + } + else + { + // - RX FIFO Half Full detected - + // Read out the RX FIFO and set fixed mode if less + // than 255 bytes to receive + + // - Length mode - + // Set fixed packet length mode if less than 256 bytes + if ((bytesLeft < 256 ) && !fixedLengthMode) + { + spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode + fixedLengthMode = true; + } + + // Read out the RX FIFO + // Do not empty the FIFO (See the CC110x or 2500 Errata Note) + spiReadBurst(RXFIFO_BURST, pByteIndex, 32 - 1); + + bytesLeft -= (32 - 1); + pByteIndex += (32 - 1); + } + } + } + + // Detect falling edge 1->0 on GDO0 + statusGDO0 = digitalRead(GPIO_GDO0_PIN); + + if (prevStatusGDO0 != statusGDO0) + { + prevStatusGDO0 = statusGDO0; + + // If GDO0 is de-asserted: RX packet complete or RX FIFO overflow + if (statusGDO0 == 0x00) + { + // There might be an RX FIFO overflow + uint8_t chipStatusBytes = spiWriteStrobe(SNOP); + + if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_RX_OVERFLOW) + { + println("RX FIFO overflow!"); + _loopState = RX_START; + break; + } + + // Check if we are in the middle of the packet reception + if (!packetStart) + { + // Complete packet received + // Read out remaining bytes in the RX FIFO + spiReadBurst(RXFIFO_BURST, pByteIndex, bytesLeft); + _loopState = RX_END; + } + } + else + { + // GDO0 asserted because sync word was received and recognized + //println("RX Syncword!"); + // wait for RX_PACKET_TIMEOUT milliseconds + // Complete packet must have been received within this time + packetStartTime = millis(); + syncStart = true; + } + } + } + break; + + case RX_END: + { + uint16_t pLen = PACKET_SIZE(packet[0]); + // Decode the first block (always 10 bytes + 2 bytes CRC) + bool decodeOk = true; + + for (uint16_t i = 1; i < pLen; i++) + { + // Check for manchester violation, abort if there is one + if (!manchDecode(&buffer[i * 2], &packet[i])) + { + println("Could not decode packet: manchester code violation"); + decodeOk = false; + break; + } + } + + if (decodeOk) + { + _rfDataLinkLayer.frameBytesReceived(&packet[0], pLen); + } + + _loopState = RX_START; + } + break; + } +} + +#endif // USE_RF + +#endif // DeviceFamily_CC13X0 diff --git a/components/knx/src/knx/rf_physical_layer_cc1101.h b/components/knx/src/knx/rf_physical_layer_cc1101.h new file mode 100644 index 0000000..02b53ce --- /dev/null +++ b/components/knx/src/knx/rf_physical_layer_cc1101.h @@ -0,0 +1,244 @@ +#pragma once + +#ifndef DeviceFamily_CC13X0 + +#include "config.h" +#ifdef USE_RF + +#include + +#include "rf_physical_layer.h" + +/*----------------------------------[standard]--------------------------------*/ +#define CC1101_TIMEOUT 2000 // Time to wait for a response from CC1101 + +#define RX_PACKET_TIMEOUT 20 // Wait 20ms for packet reception to complete +#define TX_PACKET_TIMEOUT 20 // Wait 20ms for packet reception to complete + +#ifdef __linux__ // Linux Platform + extern void delayMicroseconds (unsigned int howLong); +#endif + +/*----------------------[CC1101 - misc]---------------------------------------*/ +#define CRYSTAL_FREQUENCY 26000000 +#define CFG_REGISTER 0x2F // 47 registers +#define FIFOBUFFER 0x42 // size of Fifo Buffer +2 for rssi and lqi +#define RSSI_OFFSET_868MHZ 0x4E // dec = 74 +#define TX_RETRIES_MAX 0x05 // tx_retries_max +#define ACK_TIMEOUT 250 // ACK timeout in ms +#define CC1101_COMPARE_REGISTER 0x00 // register compare 0=no compare 1=compare +#define BROADCAST_ADDRESS 0x00 // broadcast address +#define CC1101_FREQ_315MHZ 0x01 +#define CC1101_FREQ_434MHZ 0x02 +#define CC1101_FREQ_868MHZ 0x03 +#define CC1101_FREQ_915MHZ 0x04 +#define CC1101_TEMP_ADC_MV 3.225 // 3.3V/1023 . mV pro digit +#define CC1101_TEMP_CELS_CO 2.47 // Temperature coefficient 2.47mV per Grad Celsius + +/*---------------------------[CC1101 - R/W offsets]---------------------------*/ +#define WRITE_SINGLE_BYTE 0x00 +#define WRITE_BURST 0x40 +#define READ_SINGLE_BYTE 0x80 +#define READ_BURST 0xC0 +/*---------------------------[END R/W offsets]--------------------------------*/ + +/*------------------------[CC1101 - FIFO commands]----------------------------*/ +#define TXFIFO_BURST 0x7F // write burst only +#define TXFIFO_SINGLE_BYTE 0x3F // write single only +#define RXFIFO_BURST 0xFF // read burst only +#define RXFIFO_SINGLE_BYTE 0xBF // read single only +#define PATABLE_BURST 0x7E // power control read/write +#define PATABLE_SINGLE_BYTE 0xFE // power control read/write +/*---------------------------[END FIFO commands]------------------------------*/ + +/*----------------------[CC1101 - config register]----------------------------*/ +#define IOCFG2 0x00 // GDO2 output pin configuration +#define IOCFG1 0x01 // GDO1 output pin configuration +#define IOCFG0 0x02 // GDO0 output pin configuration +#define FIFOTHR 0x03 // RX FIFO and TX FIFO thresholds +#define SYNC1 0x04 // Sync word, high byte +#define SYNC0 0x05 // Sync word, low byte +#define PKTLEN 0x06 // Packet length +#define PKTCTRL1 0x07 // Packet automation control +#define PKTCTRL0 0x08 // Packet automation control +#define DEVADDR 0x09 // Device address +#define CHANNR 0x0A // Channel number +#define FSCTRL1 0x0B // Frequency synthesizer control +#define FSCTRL0 0x0C // Frequency synthesizer control +#define FREQ2 0x0D // Frequency control word, high byte +#define FREQ1 0x0E // Frequency control word, middle byte +#define FREQ0 0x0F // Frequency control word, low byte +#define MDMCFG4 0x10 // Modem configuration +#define MDMCFG3 0x11 // Modem configuration +#define MDMCFG2 0x12 // Modem configuration +#define MDMCFG1 0x13 // Modem configuration +#define MDMCFG0 0x14 // Modem configuration +#define DEVIATN 0x15 // Modem deviation setting +#define MCSM2 0x16 // Main Radio Cntrl State Machine config +#define MCSM1 0x17 // Main Radio Cntrl State Machine config +#define MCSM0 0x18 // Main Radio Cntrl State Machine config +#define FOCCFG 0x19 // Frequency Offset Compensation config +#define BSCFG 0x1A // Bit Synchronization configuration +#define AGCCTRL2 0x1B // AGC control +#define AGCCTRL1 0x1C // AGC control +#define AGCCTRL0 0x1D // AGC control +#define WOREVT1 0x1E // High byte Event 0 timeout +#define WOREVT0 0x1F // Low byte Event 0 timeout +#define WORCTRL 0x20 // Wake On Radio control +#define FREND1 0x21 // Front end RX configuration +#define FREND0 0x22 // Front end TX configuration +#define FSCAL3 0x23 // Frequency synthesizer calibration +#define FSCAL2 0x24 // Frequency synthesizer calibration +#define FSCAL1 0x25 // Frequency synthesizer calibration +#define FSCAL0 0x26 // Frequency synthesizer calibration +#define RCCTRL1 0x27 // RC oscillator configuration +#define RCCTRL0 0x28 // RC oscillator configuration +#define FSTEST 0x29 // Frequency synthesizer cal control +#define PTEST 0x2A // Production test +#define AGCTEST 0x2B // AGC test +#define TEST2 0x2C // Various test settings +#define TEST1 0x2D // Various test settings +#define TEST0 0x2E // Various test settings +/*-------------------------[END config register]------------------------------*/ + +/*------------------------[CC1101-command strobes]----------------------------*/ +#define SRES 0x30 // Reset chip +#define SFSTXON 0x31 // Enable/calibrate freq synthesizer +#define SXOFF 0x32 // Turn off crystal oscillator. +#define SCAL 0x33 // Calibrate freq synthesizer & disable +#define SRX 0x34 // Enable RX. +#define STX 0x35 // Enable TX. +#define SIDLE 0x36 // Exit RX / TX +#define SAFC 0x37 // AFC adjustment of freq synthesizer +#define SWOR 0x38 // Start automatic RX polling sequence +#define SPWD 0x39 // Enter pwr down mode when CSn goes hi +#define SFRX 0x3A // Flush the RX FIFO buffer. +#define SFTX 0x3B // Flush the TX FIFO buffer. +#define SWORRST 0x3C // Reset real time clock. +#define SNOP 0x3D // No operation. +/*-------------------------[END command strobes]------------------------------*/ + +/*----------------------[CC1101 - status register]----------------------------*/ +#define PARTNUM 0xF0 // Part number +#define VERSION 0xF1 // Current version number +#define FREQEST 0xF2 // Frequency offset estimate +#define LQI 0xF3 // Demodulator estimate for link quality +#define RSSI 0xF4 // Received signal strength indication +#define MARCSTATE 0xF5 // Control state machine state +#define WORTIME1 0xF6 // High byte of WOR timer +#define WORTIME0 0xF7 // Low byte of WOR timer +#define PKTSTATUS 0xF8 // Current GDOx status and packet status +#define VCO_VC_DAC 0xF9 // Current setting from PLL cal module +#define TXBYTES 0xFA // Underflow and # of bytes in TXFIFO +#define RXBYTES 0xFB // Overflow and # of bytes in RXFIFO +#define RCCTRL1_STATUS 0xFC //Last RC Oscillator Calibration Result +#define RCCTRL0_STATUS 0xFD //Last RC Oscillator Calibration Result +//--------------------------[END status register]------------------------------- + +/*----------------------[CC1101 - Main Radio Control State Machine states]-----*/ +#define MARCSTATE_BITMASK 0x1F +#define MARCSTATE_SLEEP 0x00 +#define MARCSTATE_IDLE 0x01 +#define MARCSTATE_XOFF 0x02 +#define MARCSTATE_VCOON_MC 0x03 +#define MARCSTATE_REGON_MC 0x04 +#define MARCSTATE_MANCAL 0x05 +#define MARCSTATE_VCOON 0x06 +#define MARCSTATE_REGON 0x07 +#define MARCSTATE_STARTCAL 0x08 +#define MARCSTATE_BWBOOST 0x09 +#define MARCSTATE_FS_LOCK 0x0A +#define MARCSTATE_IFADCON 0x0B +#define MARCSTATE_ENDCAL 0x0C +#define MARCSTATE_RX 0x0D +#define MARCSTATE_RX_END 0x0E +#define MARCSTATE_RX_RST 0x0F +#define MARCSTATE_TXRX_SWITCH 0x10 +#define MARCSTATE_RXFIFO_OVERFLOW 0x11 +#define MARCSTATE_FSTXON 0x12 +#define MARCSTATE_TX 0x13 +#define MARCSTATE_TX_END 0x14 +#define MARCSTATE_RXTX_SWITCH 0x15 +#define MARCSTATE_TXFIFO_UNDERFLOW 0x16 + +// Chip Status Byte +// Bit fields in the chip status byte +#define CHIPSTATUS_CHIP_RDYn_BITMASK 0x80 +#define CHIPSTATUS_STATE_BITMASK 0x70 +#define CHIPSTATUS_FIFO_BYTES_AVAILABLE_BITMASK 0x0F +// Chip states +#define CHIPSTATUS_STATE_IDLE 0x00 +#define CHIPSTATUS_STATE_RX 0x10 +#define CHIPSTATUS_STATE_TX 0x20 +#define CHIPSTATUS_STATE_FSTXON 0x30 +#define CHIPSTATUS_STATE_CALIBRATE 0x40 +#define CHIPSTATUS_STATE_SETTLING 0x50 +#define CHIPSTATUS_STATE_RX_OVERFLOW 0x60 +#define CHIPSTATUS_STATE_TX_UNDERFLOW 0x70 + +// loop states +#define RX_START 0 +#define RX_ACTIVE 1 +#define RX_END 2 +#define TX_START 3 +#define TX_ACTIVE 4 +#define TX_END 5 + +class RfDataLinkLayer; + +class RfPhysicalLayerCC1101 : public RfPhysicalLayer +{ + public: + RfPhysicalLayerCC1101(RfDataLinkLayer& rfDataLinkLayer, Platform& platform); + + bool InitChip(); + void showRegisterSettings(); + void stopChip(); + void loop(); + + private: + // Table for encoding 4-bit data into a 8-bit Manchester encoding. + static const uint8_t manchEncodeTab[16]; + // Table for decoding 4-bit Manchester encoded data into 2-bit + static const uint8_t manchDecodeTab[16]; + + static const uint8_t cc1101_2FSK_32_7_kb[CFG_REGISTER]; + static const uint8_t paTablePower868[8]; + + void manchEncode(uint8_t* uncodedData, uint8_t* encodedData); + bool manchDecode(uint8_t* encodedData, uint8_t* decodedData); + + void powerDownCC1101(); + void setOutputPowerLevel(int8_t dBm); + + uint8_t sIdle(); + uint8_t sReceive(); + + void spiWriteRegister(uint8_t spi_instr, uint8_t value); + uint8_t spiReadRegister(uint8_t spi_instr); + uint8_t spiWriteStrobe(uint8_t spi_instr); + void spiReadBurst(uint8_t spi_instr, uint8_t* pArr, uint8_t len); + void spiWriteBurst(uint8_t spi_instr, const uint8_t* pArr, uint8_t len); + + uint8_t _loopState = RX_START; + + bool syncStart = false; + bool packetStart = true; + bool fixedLengthMode = false; + uint8_t* sendBuffer {0}; + uint16_t sendBufferLength {0}; + uint8_t packet[512]; + uint8_t buffer[sizeof(packet) * 2]; // We need twice the space due to manchester encoding + uint8_t* pByteIndex = &buffer[0]; + uint16_t pktLen {0}; + uint16_t bytesLeft = {0}; + uint8_t statusGDO0 {0}; + uint8_t statusGDO2 {0}; + uint8_t prevStatusGDO0 {0}; // for edge detection during polling + uint8_t prevStatusGDO2 {0}; // for edge detection during polling + uint32_t packetStartTime {0}; +}; + +#endif // USE_RF + +#endif // DeviceFamily_CC13X0 \ No newline at end of file diff --git a/components/knx/src/knx/rf_physical_layer_cc1310.cpp b/components/knx/src/knx/rf_physical_layer_cc1310.cpp new file mode 100644 index 0000000..18c2808 --- /dev/null +++ b/components/knx/src/knx/rf_physical_layer_cc1310.cpp @@ -0,0 +1,377 @@ +#ifdef DeviceFamily_CC13X0 + +#include "config.h" +#ifdef USE_RF + +#include +#include +#include + +#include "rf_physical_layer_cc1310.h" +#include "rf_data_link_layer.h" + +#include +#include DeviceFamily_constructPath(driverlib/rf_data_entry.h) +#include DeviceFamily_constructPath(driverlib/rf_prop_mailbox.h) +#include +#include "smartrf_settings/smartrf_settings.h" + +#include "cc1310_platform.h" +#include "Board.h" + +#include "bits.h" +#include "platform.h" + +#define RX_MAX_BUFFER_LENGTH 256 +#define RF_TERMINATION_EVENT_MASK (RF_EventLastCmdDone | RF_EventLastFGCmdDone | RF_EventCmdAborted | RF_EventCmdStopped | RF_EventCmdCancelled) + +#define DEBUG_DUMP_PACKETS + +static RF_Object rfObject; +static RF_Handle rfHandle; +static RF_CmdHandle rxCommandHandle; + +static uint8_t rxBuffer[sizeof(rfc_dataEntryPartial_t) + RX_MAX_BUFFER_LENGTH] __attribute__((aligned(4))); +static rfc_dataEntryPartial_t* pDataEntry = (rfc_dataEntryPartial_t*)& rxBuffer; +static dataQueue_t dataQueue; + +static uint8_t addrFilterTable[2] = {0x44, 0xFF}; // Do not modify the size without changing RF_cmdPropRxAdv.addrConf.addrSize! + +static rfc_propRxOutput_t rxStatistics; + +static uint8_t packetLength; +static uint8_t* packetDataPointer; +static int32_t packetStartTime = 0; + +static volatile bool rfDone = false; +static volatile int rfErr = 0; +static volatile int err; + +static void RxCallback(RF_Handle h, RF_CmdHandle ch, RF_EventMask e) +{ + /* + static uint32_t count = 0; + + print("count: ");println(count++); + print("nextIndex: ");println(pDataEntry->nextIndex); + //print("pktStatus.numElements: ");println(pDataEntry->pktStatus.numElements); + print("RF_cmdPropRxAdv.status: ");println(RF_cmdPropRxAdv.status, HEX); + print("pktStatus.bEntryOpen: ");println(pDataEntry->pktStatus.bEntryOpen, HEX); + print("RF_EventMask: ");println(e, HEX); + */ + + if (e & RF_EventNDataWritten) + { + // Make sure sure we are at the beginning of the packet + if (packetStartTime == 0) + { + packetStartTime = millis(); + + // pDataEntry->rxData contains the first byte of the received packet. + // Just get the address to get the start address of the receive buffer + uint8_t* pData = &pDataEntry->rxData; + + // Make sure we have a valid first block + if (((pData[1] != addrFilterTable[0]) || (pData[2] != addrFilterTable[1])) || + (crc16Dnp(pData, 10) != ((pData[10] << 8) | pData[11]))) + { + // cancel early because it does not seem to be KNX RF packet + RF_cancelCmd(rfHandle, rxCommandHandle, 0 /* force abort RF */); + return; + } + + // First block is valid, so the length is valid + uint8_t len = pDataEntry->rxData; + struct rfc_CMD_PROP_SET_LEN_s RF_cmdPropSetLen = + { + .commandNo = CMD_PROP_SET_LEN, // command identifier + .rxLen = (uint16_t)PACKET_SIZE(len) // packet length to set + }; + + //RF_runImmediateCmd(rfHandle, (uint32_t*)&RF_cmdPropSetLen); // for length > 255 + RF_Stat status = RF_runDirectCmd(rfHandle, (uint32_t)&RF_cmdPropSetLen); + + if (status != RF_StatCmdDoneSuccess) + { + println("RF CMD_PROP_SET_LEN failed!"); + } + } + } + else if (e & RF_TERMINATION_EVENT_MASK) + { + if (e & RF_EventCmdAborted) + { + println("RX ABORT"); + } + + rfDone = true; + rfErr = e & (RF_EventCmdStopped | RF_EventCmdAborted | RF_EventCmdCancelled); + } + else /* unknown reason - should not occur */ + { + pDataEntry->status = DATA_ENTRY_PENDING; + err++; + } +} + +RfPhysicalLayerCC1310::RfPhysicalLayerCC1310(RfDataLinkLayer& rfDataLinkLayer, Platform& platform) + : RfPhysicalLayer(rfDataLinkLayer, platform) +{ +} + +void RfPhysicalLayerCC1310::setOutputPowerLevel(int8_t dBm) +{ + RF_TxPowerTable_Entry* rfPowerTable = NULL; + RF_TxPowerTable_Value newValue; + uint8_t rfPowerTableSize = 0; + + // Search the default PA power table for the desired power level + newValue = RF_TxPowerTable_findValue((RF_TxPowerTable_Entry*)PROP_RF_txPowerTable, dBm); + + if (newValue.rawValue != RF_TxPowerTable_INVALID_VALUE) + { + // Found a valid entry + rfPowerTable = (RF_TxPowerTable_Entry*)PROP_RF_txPowerTable; + rfPowerTableSize = PROP_RF_txPowerTableSize; + } + + // if max power is requested then the CCFG_FORCE_VDDR_HH must be set in + // the ccfg +#if (CCFG_FORCE_VDDR_HH != 0x1) + + if ((newValue.paType == RF_TxPowerTable_DefaultPA) && + (dBm == rfPowerTable[rfPowerTableSize - 2].power)) + { + // The desired power level is set to the maximum supported under the + // default PA settings, but the boost mode (CCFG_FORCE_VDDR_HH) is not + // turned on + return; + } + +#endif + + RF_Stat rfStatus = RF_setTxPower(rfHandle, newValue); + + if (rfStatus == RF_StatSuccess) + { + print("Successfully set TX output power to: "); + println(newValue.rawValue); + } + else + { + print("Could not set TX output power to: "); + println(newValue.rawValue); + } +} + + +bool RfPhysicalLayerCC1310::InitChip() +{ + RF_Params rfParams; + RF_Params_init(&rfParams); + + pDataEntry->length = 255; + pDataEntry->config.type = DATA_ENTRY_TYPE_PARTIAL; // --> DATA_ENTRY_TYPE_PARTIAL adds a 12 Byte Header + pDataEntry-> config.irqIntv = 12; // KNX-RF first block consists of 12 bytes (one length byte, 0x44, 0xFF, one RFinfo byte, six Serial/DoA bytes, two CRC bytes) + pDataEntry-> config.lenSz = 0; // no length indicator at beginning of data entry + pDataEntry->status = DATA_ENTRY_PENDING; + pDataEntry->pNextEntry = (uint8_t*)pDataEntry; + + dataQueue.pCurrEntry = (uint8_t*)pDataEntry; + dataQueue.pLastEntry = NULL; + + // Set buffer with address. We use the two fixed bytes 0x44 and 0xFF as our address to let the + // packet engine do more filtering on itself + RF_cmdPropRxAdv.pAddr = (uint8_t*)&addrFilterTable; + // Set the Data Entity queue for received data + RF_cmdPropRxAdv.pQueue = &dataQueue; + // Set the output buffer for RX packet statistics + RF_cmdPropRxAdv.pOutput = (uint8_t*)&rxStatistics; + + // Request access to the radio + rfHandle = RF_open(&rfObject, &RF_prop, (RF_RadioSetup*)&RF_cmdPropRadioDivSetup, &rfParams); + + /* Set the frequency */ + RF_runCmd(rfHandle, (RF_Op*)&RF_cmdFs, RF_PriorityNormal, NULL, 0); + return true; +} + +void RfPhysicalLayerCC1310::stopChip() +{ + RF_cancelCmd(rfHandle, rxCommandHandle, 0 /* do not stop gracefully, instead hard abort RF */); + RF_pendCmd(rfHandle, rxCommandHandle, RF_TERMINATION_EVENT_MASK); + RF_yield(rfHandle); + RF_close(rfHandle); +} + +void RfPhysicalLayerCC1310::loop() +{ + switch (_loopState) + { + case TX_START: + { + uint8_t* sendBuffer {nullptr}; + uint16_t sendBufferLength {0}; + + //println("TX_START..."); + _rfDataLinkLayer.loadNextTxFrame(&sendBuffer, &sendBufferLength); + uint16_t pktLen = PACKET_SIZE(sendBuffer[0]); + + if (pktLen != sendBufferLength) + { + print("Error TX: SendBuffer[0]="); + println(sendBuffer[0]); + print("Error TX: SendBufferLength="); + println(sendBufferLength); + print("Error TX: PACKET_SIZE="); + println(PACKET_SIZE(sendBuffer[0])); + } + + // Calculate total number of bytes in the KNX RF packet from L-field + // Check for valid length + if ((pktLen == 0) || (pktLen > 290)) + { + println("TX packet length error!"); + break; + } + + if (pktLen > 255) + { + println("Unhandled: TX packet > 255"); + break; + } + + RF_cmdPropTx.pktLen = pktLen; + RF_cmdPropTx.pPkt = sendBuffer; + RF_cmdPropTx.startTrigger.triggerType = TRIG_NOW; + RF_EventMask result = RF_runCmd(rfHandle, (RF_Op*)&RF_cmdPropTx, RF_PriorityNormal, NULL, RF_TERMINATION_EVENT_MASK); + //print("TX: RF_EventMask: ");println(result, HEX); + +#if defined(DEBUG_DUMP_PACKETS) + printHex("TX: ", sendBuffer, pktLen); +#endif + delete sendBuffer; + + if (result != RF_EventLastCmdDone) + { + print("Unexpected result command: "); + println(result, HEX); + } + + //println("Restart RX..."); + _loopState = RX_START; + } + break; + + case RX_START: + { + packetStartTime = 0; + rfDone = false; + rfErr = 0; + err = 0; + pDataEntry->status = DATA_ENTRY_PENDING; + + rxCommandHandle = RF_postCmd(rfHandle, (RF_Op*)&RF_cmdPropRxAdv, RF_PriorityNormal, &RxCallback, RF_EventNDataWritten | RF_EventRxAborted); + + if (rxCommandHandle == RF_ALLOC_ERROR) + { + println("Error: nRF_pendCmd() failed"); + return; + } + + _loopState = RX_ACTIVE; + } + break; + + case RX_ACTIVE: + { + if (!_rfDataLinkLayer.isTxQueueEmpty()) + { + RF_cancelCmd(rfHandle, rxCommandHandle, RF_ABORT_GRACEFULLY); + RF_pendCmd(rfHandle, rxCommandHandle, RF_TERMINATION_EVENT_MASK); + + if (RF_cmdPropTx.status != PROP_DONE_OK) + { + print("Unexpected RF_cmdPropTx.status after stopping RX: "); + println(RF_cmdPropTx.status, HEX); + } + + _loopState = TX_START; + break; + } + + // Check if we have an incomplete packet reception + if (!rfDone && ((packetStartTime > 0) && (millis() - packetStartTime > RX_PACKET_TIMEOUT))) + { + println("RX packet timeout!"); + RF_cancelCmd(rfHandle, rxCommandHandle, RF_ABORT_GRACEFULLY); + RF_pendCmd(rfHandle, rxCommandHandle, RF_TERMINATION_EVENT_MASK); + /* + print("nRxOk = ");println(rxStatistics.nRxOk); // Number of packets that have been received with payload, CRC OK and not ignored + print("nRxNok = ");println(rxStatistics.nRxNok); // Number of packets that have been received with CRC error + print("nRxIgnored = ");println(rxStatistics.nRxIgnored); // Number of packets that have been received with CRC OK and ignored due to address mismatch + print("nRxStopped = ");println(rxStatistics.nRxStopped); // Number of packets not received due to illegal length or address mismatch with pktConf.filterOp = 1 + print("nRxBufFull = ");println(rxStatistics.nRxBufFull); // Number of packets that have been received and discarded due to lack of buffer space + */ + _loopState = RX_START; + break; + } + else if (rfDone) + { + RF_EventMask result = RF_pendCmd(rfHandle, rxCommandHandle, RF_TERMINATION_EVENT_MASK); + + if ((result & RF_EventCmdCancelled) || (result & RF_EventCmdStopped) || (result & RF_EventCmdAborted)) + { + println("RF terminated because of RF_flushCmd() or RF_cancelCmd()"); + } + else if ((result & RF_EventLastCmdDone) != RF_EventLastCmdDone) + { + print("Unexpected Rx result command: "); + println(result, HEX); + } + else if (rfErr) + { + println("Rx is no KNX frame"); + } + else if ((result & RF_EventLastCmdDone) == RF_EventLastCmdDone) + { + // add CRC sizes for received blocks, but do not add the length of the L-field (1 byte) itself + packetLength = PACKET_SIZE(pDataEntry->rxData); + packetDataPointer = (uint8_t*) &pDataEntry->rxData; + + // Sanity check: the partial data entry index points to the next free location in the partial RX buffer + if (packetLength != (pDataEntry->nextIndex - 1)) + { + println("Mismatch between packetLength and pDataEntry->nextIndex: "); + print("packetLength = "); + print(packetLength); + print(", pDataEntry->nextIndex = "); + println(pDataEntry->nextIndex); + } + + /* + print("nRxOk = ");println(rxStatistics.nRxOk); // Number of packets that have been received with payload, CRC OK and not ignored + print("nRxNok = ");println(rxStatistics.nRxNok); // Number of packets that have been received with CRC error + print("nRxIgnored = ");println(rxStatistics.nRxIgnored); // Number of packets that have been received with CRC OK and ignored due to address mismatch + print("nRxStopped = ");println(rxStatistics.nRxStopped); // Number of packets not received due to illegal length or address mismatch with pktConf.filterOp = 1 + print("nRxBufFull = ");println(rxStatistics.nRxBufFull); // Number of packets that have been received and discarded due to lack of buffer space + */ + +#if defined(DEBUG_DUMP_PACKETS) + printHex("RX: ", packetDataPointer, packetLength, false); + print ("- RSSI: "); + println(rxStatistics.lastRssi); +#endif + _rfDataLinkLayer.frameBytesReceived(packetDataPointer, packetLength); + } + + _loopState = RX_START; + } + } + break; + } +} + +#endif // USE_RF + +#endif // DeviceFamily_CC13X0 diff --git a/components/knx/src/knx/rf_physical_layer_cc1310.h b/components/knx/src/knx/rf_physical_layer_cc1310.h new file mode 100644 index 0000000..9c79e96 --- /dev/null +++ b/components/knx/src/knx/rf_physical_layer_cc1310.h @@ -0,0 +1,41 @@ +#pragma once + +#ifdef DeviceFamily_CC13X0 + +#include "config.h" +#ifdef USE_RF + +#include + +#include "rf_physical_layer.h" + +#define RX_PACKET_TIMEOUT 20 // Wait 20ms for packet reception to complete + +// loop states +#define RX_START 0 +#define RX_ACTIVE 1 +#define RX_END 2 +#define TX_START 3 +#define TX_ACTIVE 4 +#define TX_END 5 + +class RfDataLinkLayer; + +class RfPhysicalLayerCC1310 : public RfPhysicalLayer +{ + public: + RfPhysicalLayerCC1310(RfDataLinkLayer& rfDataLinkLayer, Platform& platform); + + bool InitChip() override; + void stopChip() override; + void loop() override; + + void setOutputPowerLevel(int8_t dBm); + + private: + uint8_t _loopState = RX_START; +}; + +#endif // USE_RF + +#endif // DeviceFamily_CC13X0 \ No newline at end of file diff --git a/components/knx/src/knx/router_object.cpp b/components/knx/src/knx/router_object.cpp new file mode 100644 index 0000000..36ebb3a --- /dev/null +++ b/components/knx/src/knx/router_object.cpp @@ -0,0 +1,603 @@ +#include "config.h" + +#include +#include "router_object.h" +#include "bits.h" +#include "memory.h" +#include "data_property.h" +#include "callback_property.h" +#include "function_property.h" + + +// Filter Table Realization Type 3 +// The Filter Table Realisation Type 3 shall be organised as a memory mapped bit-field of +// 65536 bits and thus 8 192 octets. Each bit shall uniquely correspond to one Group Address. +// The full 16 bit KNX GA encoding range shall be supported. +// +// octet_address = GA_value div 8 +// bit_position = GA_value mod 8 +static constexpr uint16_t kFilterTableSize = 65536 / 8; // Each group address is represented by one bit + +enum RouteTableServices +{ + ClearRoutingTable = 0x01, // no info bytes + SetRoutingTable = 0x02, // no info bytes + ClearGroupAddress = 0x03, // 4 bytes: start address and end address + SetGroupAddress = 0x04, // 4 bytes: start address and end address +}; + +RouterObject::RouterObject(Memory& memory, uint32_t staticTableAdr, uint32_t staticTableSize) + : TableObject(memory, staticTableAdr, staticTableSize) +{ +} + +void RouterObject::initialize1x(DptMedium mediumType, uint16_t maxApduSize) +{ + // Object index property is not included for coupler model 1.x, so value is "don't care". + initialize(CouplerModel::Model_1x, 200, mediumType, RouterObjectType::Single, maxApduSize); +} + +void RouterObject::initialize20(uint8_t objIndex, DptMedium mediumType, RouterObjectType rtType, uint16_t maxApduSize) +{ + initialize(CouplerModel::Model_20, objIndex, mediumType, rtType, maxApduSize); +} + +void RouterObject::initialize(CouplerModel model, uint8_t objIndex, DptMedium mediumType, RouterObjectType rtType, uint16_t maxApduSize) +{ + bool useHopCount = false; + bool useTable = true; + _model = model; + + if (model == CouplerModel::Model_20) + { + useHopCount = (rtType == RouterObjectType::Primary); + useTable = (rtType == RouterObjectType::Secondary); + } + + // These properties are always present + Property* fixedProperties[] = + { + new DataProperty( PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t) OT_ROUTER ), + new DataProperty( PID_MEDIUM_STATUS, false, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0, (uint16_t) 0 ), // 0 means communication is possible, could be set by datalink layer or bau to 1 (comm impossible) + new DataProperty( PID_MAX_APDU_LENGTH_ROUTER, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, maxApduSize ), + }; + uint8_t fixedPropertiesCount = sizeof(fixedProperties) / sizeof(Property*); + + // Only present if coupler model is 1.x + Property* model1xProperties[] = + { + // default values from Spec, see 03_05_01 4.4.4 and 4.4.5 + new DataProperty( PID_MAIN_LCCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL) ), // Primary: data individual (connless and connorient) + broadcast + new DataProperty( PID_SUB_LCCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL) ), // Secondary: data individual (connless and connorient) + broadcast + new DataProperty( PID_MAIN_LCGRPCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT)), // Primary: data group + new DataProperty( PID_SUB_LCGRPCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT)), // Secondary: data group + }; + uint8_t model1xPropertiesCount = sizeof(model1xProperties) / sizeof(Property*); + + // Only present if coupler model is 2.0 + // One router object per interface, currently only TP1/RF coupler specified + Property* model20Properties[] = + { + new DataProperty( PID_OBJECT_INDEX, false, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv0, objIndex ), // Must be set by concrete BAUxxxx! + new DataProperty( PID_MEDIUM, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0, (uint8_t) mediumType ), + }; + uint8_t model20PropertiesCount = sizeof(model20Properties) / sizeof(Property*); + + Property* tableProperties[] = + { + new FunctionProperty(this, PID_ROUTETABLE_CONTROL, + // Command Callback of PID_ROUTETABLE_CONTROL + [](RouterObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void { + obj->functionRouteTableControl(true, data, length, resultData, resultLength); + }, + // State Callback of PID_ROUTETABLE_CONTROL + [](RouterObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void { + obj->functionRouteTableControl(false, data, length, resultData, resultLength); + }) + }; + + Property* tableProperties20[] = + { + new DataProperty( PID_COUPLER_SERVICES_CONTROL, true, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0, (uint8_t) 0), // written by ETS TODO: implement + new DataProperty( PID_FILTER_TABLE_USE, true, PDT_BINARY_INFORMATION, 1, ReadLv3 | WriteLv0, (uint16_t) 0 ) // default: invalid filter table, do not use, written by ETS + }; + + uint8_t tablePropertiesCount = sizeof(tableProperties) / sizeof(Property*); + uint8_t tableProperties20Count = sizeof(tableProperties20) / sizeof(Property*); + + size_t allPropertiesCount = fixedPropertiesCount; + allPropertiesCount += (model == CouplerModel::Model_1x) ? model1xPropertiesCount : model20PropertiesCount; + allPropertiesCount += useHopCount ? 1 : 0; + allPropertiesCount += useTable ? tablePropertiesCount : 0; + allPropertiesCount += useTable && (model == CouplerModel::Model_20) ? tableProperties20Count : 0; + allPropertiesCount += ((mediumType == DptMedium::KNX_RF) || (mediumType == DptMedium::KNX_IP)) ? 1 : 0; // PID_RF_ENABLE_SBC and PID_IP_ENABLE_SBC + + Property* allProperties[allPropertiesCount]; + + memcpy(&allProperties[0], &fixedProperties[0], sizeof(fixedProperties)); + + uint8_t i = fixedPropertiesCount; + + if (model == CouplerModel::Model_1x) + { + memcpy(&allProperties[i], model1xProperties, sizeof(model1xProperties)); + i += model1xPropertiesCount; + } + else + { + memcpy(&allProperties[i], model20Properties, sizeof(model20Properties)); + i += model20PropertiesCount; + } + + if (useHopCount) + { + // TODO: Primary side: 5 for line coupler, 4 for backbone coupler, only exists if secondary is open medium without hop count + // Do we need to set a default value here or is it written by ETS? + allProperties[i++] = new DataProperty( PID_HOP_COUNT, true, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t) 5); + } + + if (useTable) + { + memcpy(&allProperties[i], tableProperties, sizeof(tableProperties)); + i += tablePropertiesCount; + + if ((model == CouplerModel::Model_20)) + { + memcpy(&allProperties[i], tableProperties20, sizeof(tableProperties20)); + i += tableProperties20Count; + } + } + + if (mediumType == DptMedium::KNX_RF) + { + allProperties[i++] = new FunctionProperty(this, PID_RF_ENABLE_SBC, + // Command Callback of PID_RF_ENABLE_SBC + [](RouterObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void + { + obj->functionRfEnableSbc(true, data, length, resultData, resultLength); + }, + // State Callback of PID_RF_ENABLE_SBC + [](RouterObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void + { + obj->functionRfEnableSbc(false, data, length, resultData, resultLength); + }); + } + else if (mediumType == DptMedium::KNX_IP) + { + allProperties[i++] = new FunctionProperty(this, PID_IP_ENABLE_SBC, + // Command Callback of PID_IP_ENABLE_SBC + [](RouterObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void + { + obj->functionIpEnableSbc(true, data, length, resultData, resultLength); + }, + // State Callback of PID_IP_ENABLE_SBC + [](RouterObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void + { + obj->functionIpEnableSbc(false, data, length, resultData, resultLength); + }); + } + + if (useTable) + TableObject::initializeProperties(sizeof(allProperties), allProperties); + else + InterfaceObject::initializeProperties(sizeof(allProperties), allProperties); +} + +const uint8_t* RouterObject::restore(const uint8_t* buffer) +{ + return TableObject::restore(buffer); +} + +void RouterObject::commandClearSetRoutingTable(bool bitIsSet) +{ + uint8_t fillbyte = bitIsSet ? 0xFF : 0x00; + uint32_t relptr = _memory.toRelative(data()); +#ifdef KNX_LOG_COUPLER + print("RouterObject::commandClearSetRoutingTable "); + println(bitIsSet); + println(relptr); + println((uint32_t)data()); +#endif + + for (uint16_t i = 0; i < kFilterTableSize; i++) + { + _memory.writeMemory(relptr + i, 1, &fillbyte); + } +} + +bool RouterObject::statusClearSetRoutingTable(bool bitIsSet) +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::statusClearSetRoutingTable "); + println(bitIsSet); +#endif + + for (uint16_t i = 0; i < kFilterTableSize; i++) + { + if (data()[i] != (bitIsSet ? 0xFF : 0x00)) + return false; + } + + return true; +} + +void RouterObject::commandClearSetGroupAddress(uint16_t startAddress, uint16_t endAddress, bool bitIsSet) +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::commandClearSetGroupAddress "); + print(startAddress); + print(" "); + print(endAddress); + print(" "); + println(bitIsSet); +#endif + + uint16_t startOctet = startAddress / 8; + uint8_t startBitPosition = startAddress % 8; + uint16_t endOctet = endAddress / 8; + uint8_t endBitPosition = endAddress % 8; + + if (startOctet == endOctet) + { + uint32_t relptr = _memory.toRelative(data()) + startOctet; + uint8_t octetData = 0; // = data()[startOctet]; + _memory.readMemory(relptr, 1, &octetData); + + for (uint8_t bitPos = startBitPosition; bitPos <= endBitPosition; bitPos++) + { + if (bitIsSet) + octetData |= 1 << bitPos; + else + octetData &= ~(1 << bitPos); + } + + _memory.writeMemory(relptr, 1, &octetData); + return; + } + + for (uint16_t i = startOctet; i <= endOctet; i++) + { + uint32_t relptr = _memory.toRelative(data()) + i; + uint8_t octetData = 0; + _memory.readMemory(relptr, 1, &octetData); + + if (i == startOctet) + { + for (uint8_t bitPos = startBitPosition; bitPos <= 7; bitPos++) + { + if (bitIsSet) + octetData |= 1 << bitPos; + else + octetData &= ~(1 << bitPos); + } + } + else if (i == endOctet) + { + for (uint8_t bitPos = 0; bitPos <= endBitPosition; bitPos++) + { + if (bitIsSet) + octetData |= 1 << bitPos; + else + octetData &= ~(1 << bitPos); + } + } + else + { + if (bitIsSet) + octetData = 0xFF; + else + octetData = 0x00; + } + + _memory.writeMemory(relptr, 1, &octetData); + } +} + +bool RouterObject::statusClearSetGroupAddress(uint16_t startAddress, uint16_t endAddress, bool bitIsSet) +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::statusClearSetGroupAddress "); + print(startAddress); + print(" "); + print(endAddress); + print(" "); + println(bitIsSet); +#endif + + uint16_t startOctet = startAddress / 8; + uint8_t startBitPosition = startAddress % 8; + uint16_t endOctet = endAddress / 8; + uint8_t endBitPosition = endAddress % 8; + + if (startOctet == endOctet) + { + for (uint8_t bitPos = startBitPosition; bitPos <= endBitPosition; bitPos++) + { + if (bitIsSet) + { + if ((data()[startOctet] & (1 << bitPos)) == 0) + return false; + } + else + { + if ((data()[startOctet] & (1 << bitPos)) != 0) + return false; + } + } + + return true; + } + + for (uint16_t i = startOctet; i <= endOctet; i++) + { + if (i == startOctet) + { + for (uint8_t bitPos = startBitPosition; bitPos <= 7; bitPos++) + { + if (bitIsSet) + { + if ((data()[i] & (1 << bitPos)) == 0) + return false; + } + else + { + if ((data()[i] & (1 << bitPos)) != 0) + return false; + } + } + } + else if (i == endOctet) + { + for (uint8_t bitPos = 0; bitPos <= endBitPosition; bitPos++) + { + if (bitIsSet) + { + if ((data()[i] & (1 << bitPos)) == 0) + return false; + } + else + { + if ((data()[i] & (1 << bitPos)) != 0) + return false; + } + } + } + else + { + if (data()[i] != (bitIsSet ? 0xFF : 0x00)) + return false; + } + } + + return true; +} + +void RouterObject::functionRouteTableControl(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::functionRouteTableControl "); + print(isCommand); + print(" "); + printHex("", data, length); +#endif + + RouteTableServices srvId = (RouteTableServices) data[1]; + + if (isCommand) + { + if (loadState() != LS_LOADING) + { + println("access violation. filter table can only be modified in LS_LOADING"); + resultData[0] = ReturnCodes::AccessReadOnly; + resultData[1] = srvId; + resultLength = 2; + return; + } + + switch (srvId) + { + case ClearRoutingTable: + commandClearSetRoutingTable(false); + resultData[0] = ReturnCodes::Success; + resultData[1] = srvId; + resultLength = 2; + return; + + case SetRoutingTable: + commandClearSetRoutingTable(true); + resultData[0] = ReturnCodes::Success; + resultData[1] = srvId; + resultLength = 2; + return; + + case ClearGroupAddress: + { + uint16_t startAddress; + uint16_t endAddress; + popWord(startAddress, &data[2]); + popWord(endAddress, &data[4]); + commandClearSetGroupAddress(startAddress, endAddress, false); + resultData[0] = ReturnCodes::Success; + resultData[1] = srvId; + pushWord(startAddress, &resultData[2]); + pushWord(endAddress, &resultData[4]); + resultLength = 6; + return; + } + + case SetGroupAddress: + { + uint16_t startAddress; + uint16_t endAddress; + popWord(startAddress, &data[2]); + popWord(endAddress, &data[4]); + commandClearSetGroupAddress(startAddress, endAddress, true); + resultData[0] = ReturnCodes::Success; + resultData[1] = srvId; + pushWord(startAddress, &resultData[2]); + pushWord(endAddress, &resultData[4]); + resultLength = 6; + return; + } + } + } + else + { + switch (srvId) + { + case ClearRoutingTable: + resultData[0] = statusClearSetRoutingTable(false) ? ReturnCodes::Success : ReturnCodes::GenericError; + resultData[1] = srvId; + resultLength = 2; + return; + + case SetRoutingTable: + resultData[0] = statusClearSetRoutingTable(true) ? ReturnCodes::Success : ReturnCodes::GenericError; + resultData[1] = srvId; + resultLength = 2; + return; + + case ClearGroupAddress: + { + uint16_t startAddress; + uint16_t endAddress; + popWord(startAddress, &data[2]); + popWord(endAddress, &data[4]); + resultData[0] = statusClearSetGroupAddress(startAddress, endAddress, false) ? ReturnCodes::Success : ReturnCodes::GenericError; + resultData[1] = srvId; + pushWord(startAddress, &resultData[2]); + pushWord(endAddress, &resultData[4]); + resultLength = 6; + return; + } + + case SetGroupAddress: + { + uint16_t startAddress; + uint16_t endAddress; + popWord(startAddress, &data[2]); + popWord(endAddress, &data[4]); + resultData[0] = statusClearSetGroupAddress(startAddress, endAddress, true) ? ReturnCodes::Success : ReturnCodes::GenericError; + resultData[1] = srvId; + pushWord(startAddress, &resultData[2]); + pushWord(endAddress, &resultData[4]); + resultLength = 6; + return; + } + } + } + + // We should not get here + resultData[0] = ReturnCodes::GenericError; + resultData[1] = srvId; + resultLength = 2; +} + +void RouterObject::functionRfEnableSbc(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) +{ + if (isCommand) + { + _rfSbcRoutingEnabled = (data[0] == 1) ? true : false; + } + + resultData[0] = ReturnCodes::Success; + resultData[1] = _rfSbcRoutingEnabled ? 1 : 0; + resultLength = 2; +} + +bool RouterObject::isRfSbcRoutingEnabled() +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::isRfSbcRoutingEnabled "); + println(_rfSbcRoutingEnabled); +#endif + return _rfSbcRoutingEnabled; +} + +// TODO: check if IP SBC works the same way, just copied from RF +void RouterObject::functionIpEnableSbc(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::functionIpEnableSbc "); + print(isCommand); + printHex(" ", data, length); +#endif + + if (isCommand) + { + _ipSbcRoutingEnabled = (data[0] == 1) ? true : false; + } + + resultData[0] = ReturnCodes::Success; + resultData[1] = _ipSbcRoutingEnabled ? 1 : 0; + resultLength = 2; +} + +// TODO: check if IP SBC works the same way, just copied from RF +bool RouterObject::isIpSbcRoutingEnabled() +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::isIpSbcRoutingEnabled "); + println(_ipSbcRoutingEnabled); +#endif + return _ipSbcRoutingEnabled; +} + +void RouterObject::beforeStateChange(LoadState& newState) +{ +#ifdef KNX_LOG_COUPLER + println("RouterObject::beforeStateChange"); +#endif + + if (newState != LS_LOADED) + return; +} + +void RouterObject::masterReset(EraseCode eraseCode, uint8_t channel) +{ +#ifdef KNX_LOG_COUPLER + print("RouterObject::masterReset "); + print(eraseCode); + print(" "); + println(channel); +#endif + + if (eraseCode == FactoryReset) + { + // TODO: handle different erase codes + println("Factory reset of router object with filter table requested."); + } +} + +bool RouterObject::isGroupAddressInFilterTable(uint16_t groupAddress) +{ + if (loadState() != LS_LOADED) + return false; + + uint8_t filterTableUse = 0x01; + Property* propFilterTableUse = property(PID_FILTER_TABLE_USE); + + if (propFilterTableUse) // check if property PID_FILTER_TABLE_USE exists (only coupler 20), if not, ignore this + if (propFilterTableUse->read(filterTableUse) == 0) // check if property PID_FILTER_TABLE_USE is empty, if so, return false + return false; + + if ((filterTableUse & 0x01) == 1) + { + uint8_t* filterTable = data(); + // octet_address = GA_value div 8 + // bit_position = GA_value mod 8 + uint16_t octetAddress = groupAddress / 8; + uint8_t bitPosition = groupAddress % 8; + + + if (filterTable) + return (filterTable[octetAddress] & (1 << bitPosition)) == (1 << bitPosition); + else + { + println("RouterObject::isGroupAddressInFilterTable filterTable is NULL"); + return false; + } + } + + return false; +} diff --git a/components/knx/src/knx/router_object.h b/components/knx/src/knx/router_object.h new file mode 100644 index 0000000..7f9194d --- /dev/null +++ b/components/knx/src/knx/router_object.h @@ -0,0 +1,58 @@ +#pragma once + +#include "config.h" + +#include "table_object.h" +#include "knx_types.h" + +class Memory; + +enum CouplerModel +{ + Model_1x, + Model_20 +}; + +enum RouterObjectType +{ + Primary, + Secondary, + Single // Not used, just a placeholder for better readability for coupler model 1.x +}; + +class RouterObject : public TableObject +{ + public: + RouterObject(Memory& memory, uint32_t staticTableAdr = 0, uint32_t staticTableSize = 0); + + void initialize1x(DptMedium mediumType, uint16_t maxApduSize); + void initialize20(uint8_t objIndex, DptMedium mediumType, RouterObjectType rtType, uint16_t maxApduSize); + void initialize(CouplerModel model, uint8_t objIndex, DptMedium mediumType, RouterObjectType rtType, uint16_t maxApduSize); + + bool isGroupAddressInFilterTable(uint16_t groupAddress); + + bool isRfSbcRoutingEnabled(); + bool isIpSbcRoutingEnabled(); + + void masterReset(EraseCode eraseCode, uint8_t channel) override; + + const uint8_t* restore(const uint8_t* buffer) override; + + protected: + void beforeStateChange(LoadState& newState) override; + + private: + // Function properties + void functionRouteTableControl(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength); + void functionRfEnableSbc(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength); + void functionIpEnableSbc(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength); + + void commandClearSetRoutingTable(bool bitIsSet); + bool statusClearSetRoutingTable(bool bitIsSet); + void commandClearSetGroupAddress(uint16_t startAddress, uint16_t endAddress, bool bitIsSet); + bool statusClearSetGroupAddress(uint16_t startAddress, uint16_t endAddress, bool bitIsSet); + + bool _rfSbcRoutingEnabled = false; + bool _ipSbcRoutingEnabled = false; + CouplerModel _model = CouplerModel::Model_20; +}; diff --git a/components/knx/src/knx/save_restore.h b/components/knx/src/knx/save_restore.h new file mode 100644 index 0000000..12d0717 --- /dev/null +++ b/components/knx/src/knx/save_restore.h @@ -0,0 +1,43 @@ +#pragma once +#include + +/** + * Interface for classes that can save and restore data from a buffer. + */ +class SaveRestore +{ + public: + /** + * This method is called when the object should save its state to the buffer. + * + * @param buffer The buffer the object should save its state to. + * + * @return The buffer plus the size of the object state. The next object will use this value as + * the start of its buffer. + */ + virtual uint8_t* save(uint8_t* buffer) + { + return buffer; + } + + /** + * This method is called when the object should restore its state from the buffer. + * + * @param buffer The buffer the object should restore its state from. + * + * @return The buffer plus the size of the object state. The next object will use this value as + * the start of its buffer. + */ + virtual const uint8_t* restore(const uint8_t* buffer) + { + return buffer; + } + + /** + * @return The maximum number of bytes the object needs to save its state. + */ + virtual uint16_t saveSize() + { + return 0; + } +}; \ No newline at end of file diff --git a/components/knx/src/knx/secure_application_layer.cpp b/components/knx/src/knx/secure_application_layer.cpp new file mode 100644 index 0000000..315f5cc --- /dev/null +++ b/components/knx/src/knx/secure_application_layer.cpp @@ -0,0 +1,1336 @@ +#include "config.h" +#ifdef USE_DATASECURE + +#include "secure_application_layer.h" +#include "transport_layer.h" +#include "cemi_frame.h" +#include "association_table_object.h" +#include "address_table_object.h" +#include "security_interface_object.h" +#include "device_object.h" +#include "apdu.h" +#include "bau.h" +#include "string.h" +#include "bits.h" + +// Select what cipher modes to include. We need AES128-CBC and AES128-CTR modes. +#define CBC 1 +#define CTR 1 +#define ECB 0 +#include "aes.hpp" + +static constexpr uint8_t kSecureDataPdu = 0; +static constexpr uint8_t kSecureSyncRequest = 2; +static constexpr uint8_t kSecureSyncResponse = 3; + +SecureApplicationLayer::SecureApplicationLayer(DeviceObject& deviceObj, SecurityInterfaceObject& secIfObj, BusAccessUnit& bau): + ApplicationLayer(bau), + _secIfObj(secIfObj), + _deviceObj(deviceObj) +{ +} + +void SecureApplicationLayer::groupAddressTable(AddressTableObject& addrTable) +{ + _addrTab = &addrTable; +} + +/* from transport layer */ + +void SecureApplicationLayer::dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu) +{ + if (_addrTab == nullptr) + return; + + println("dataGroupIndication"); + + if (apdu.type() == SecureService) + { + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataGroupIndication(hopType, priority, tsap, plainFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataGroupIndication(hopType, priority, tsap, apdu); +} + +void SecureApplicationLayer::dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status) +{ + println("dataGroupConfirm"); + + if (apdu.type() == SecureService) + { + // We do not care about confirmations of our sync communication + if (isSyncService(apdu)) + { + return; + } + + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataGroupConfirm(ack, hopType, priority, tsap, plainFrame.apdu(), secCtrl, status); + } + + return; + } + + ApplicationLayer::dataGroupConfirm(ack, hopType, priority, tsap, apdu, status); +} + +void SecureApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) +{ + println("dataBroadcastIndication"); + + if (apdu.type() == SecureService) + { + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataBroadcastIndication(hopType, priority, source, plainFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataBroadcastIndication(hopType, priority, source, apdu); +} + +void SecureApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, bool status) +{ + println("dataBroadcastConfirm"); + + if (apdu.type() == SecureService) + { + // We do not care about confirmations of our sync communication + if (isSyncService(apdu)) + { + return; + } + + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataBroadcastConfirm(ack, hopType, priority, plainFrame.apdu(), secCtrl, status); + } + + return; + } + + ApplicationLayer::dataBroadcastConfirm(ack, hopType, priority, apdu, status); +} + +void SecureApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) +{ + println("dataSystemBroadcastIndication"); + + if (apdu.type() == SecureService) + { + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataSystemBroadcastIndication(hopType, priority, source, plainFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataSystemBroadcastIndication(hopType, priority, source, apdu); +} + +void SecureApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status) +{ + println("dataSystemBroadcastConfirm"); + + if (apdu.type() == SecureService) + { + // We do not care about confirmations of our sync communication + if (isSyncService(apdu)) + { + return; + } + + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataSystemBroadcastConfirm(hopType, priority, plainFrame.apdu(), secCtrl, status); + } + + return; + } + + ApplicationLayer::dataSystemBroadcastConfirm(hopType, priority, apdu, status); +} + +void SecureApplicationLayer::dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) +{ + println("dataIndividualIndication"); + + if (apdu.type() == SecureService) + { + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataIndividualIndication(hopType, priority, source, plainFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataIndividualIndication(hopType, priority, source, apdu); +} + +void SecureApplicationLayer::dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, bool status) +{ + println("dataIndividualConfirm"); + + if (apdu.type() == SecureService) + { + // We do not care about confirmations of our sync communication + if (isSyncService(apdu)) + { + return; + } + + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataIndividualConfirm(ack, hopType, priority, source, apdu, secCtrl, status); + } + + return; + } + else + { + ApplicationLayer::dataIndividualConfirm(ack, hopType, priority, source, apdu, status); + } +} + +void SecureApplicationLayer::dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu) +{ + println("dataConnectedIndication"); + + if (apdu.type() == SecureService) + { + // Will be filled in by decodeSecureApdu() + SecurityControl secCtrl; + + // Decrypt secure APDU + // Somehow ugly that we need to know the size in advance here at this point + uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac) + CemiFrame plainFrame(plainApduLength); + + // Decrypt secure APDU + if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl)) + { + // Process decrypted inner APDU + ApplicationLayer::dataConnectedIndication(priority, tsap, plainFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataConnectedIndication(priority, tsap, apdu); +} + +void SecureApplicationLayer::dataConnectedConfirm(uint16_t tsap) +{ + // Just the confirmation issued by the transport layer in case of T_DATA_CONNECTED + ApplicationLayer::dataConnectedConfirm(tsap); +} + +/* to transport layer */ + +void SecureApplicationLayer::dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl) +{ + if (_addrTab == nullptr) + return; + + println("dataGroupRequest"); + + if (secCtrl.dataSecurity != DataSecurity::None) + { + apdu.frame().sourceAddress(_deviceObj.individualAddress()); + apdu.frame().destinationAddress(_addrTab->getGroupAddress(tsap)); + apdu.frame().addressType(GroupAddress); + + uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4 + CemiFrame secureFrame(secureApduLength); + + // create secure APDU + if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl)) + { + ApplicationLayer::dataGroupRequest(ack, hopType, priority, tsap, secureFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataGroupRequest(ack, hopType, priority, tsap, apdu, secCtrl); +} + +void SecureApplicationLayer::dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl) +{ + println("dataBroadcastRequest"); + + if (secCtrl.dataSecurity != DataSecurity::None) + { + apdu.frame().sourceAddress(_deviceObj.individualAddress()); + apdu.frame().destinationAddress(0x0000); + apdu.frame().addressType(GroupAddress); + apdu.frame().systemBroadcast(Broadcast); + + uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4 + CemiFrame secureFrame(secureApduLength); + + // create secure APDU + if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl)) + { + ApplicationLayer::dataBroadcastRequest(ack, hopType, SystemPriority, secureFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void SecureApplicationLayer::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl) +{ + println("dataSystemBroadcastRequest"); + + if (secCtrl.dataSecurity != DataSecurity::None) + { + apdu.frame().sourceAddress(_deviceObj.individualAddress()); + apdu.frame().destinationAddress(0x0000); + apdu.frame().addressType(GroupAddress); + apdu.frame().systemBroadcast(SysBroadcast); + + uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4 + CemiFrame secureFrame(secureApduLength); + + // create secure APDU + if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl)) + { + ApplicationLayer::dataSystemBroadcastRequest(ack, hopType, SystemPriority, secureFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataSystemBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl); +} + +void SecureApplicationLayer::dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu, const SecurityControl& secCtrl) +{ + println("dataIndividualRequest"); + + if (secCtrl.dataSecurity != DataSecurity::None) + { + apdu.frame().sourceAddress(_deviceObj.individualAddress()); + apdu.frame().destinationAddress(destination); + apdu.frame().addressType(IndividualAddress); + + uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4 + CemiFrame secureFrame(secureApduLength); + + // create secure APDU + if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl)) + { + ApplicationLayer::dataIndividualRequest(ack, hopType, priority, destination, secureFrame.apdu(), secCtrl); + } + + return; + } + + ApplicationLayer::dataIndividualRequest(ack, hopType, priority, destination, apdu, secCtrl); +} + +void SecureApplicationLayer::dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu, const SecurityControl& secCtrl) +{ + println("dataConnectedRequest"); + + if (secCtrl.dataSecurity != DataSecurity::None) + { + apdu.frame().sourceAddress(_deviceObj.individualAddress()); + apdu.frame().destinationAddress(_transportLayer->getConnectionAddress()); + apdu.frame().addressType(IndividualAddress); + + uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4 + CemiFrame secureFrame(secureApduLength); + + // create secure APDU + if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl)) + { + ApplicationLayer::dataConnectedRequest(tsap, priority, secureFrame.apdu(), secCtrl); + } + + return; + } + + // apdu must be valid until it was confirmed + ApplicationLayer::dataConnectedRequest(tsap, priority, apdu, secCtrl); +} + +void SecureApplicationLayer::encryptAesCbc(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key) +{ + // Use zeroes as IV for first round + uint8_t zeroIv[16] = {0x00}; + + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, key, zeroIv); + + // Now encrypt first block B0. + AES_CBC_encrypt_buffer(&ctx, (uint8_t*) iv, 16); + + // Encrypt remaining buffer + AES_CBC_encrypt_buffer(&ctx, buffer, bufLen); +} + +void SecureApplicationLayer::xcryptAesCtr(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key) +{ + struct AES_ctx ctx; + + AES_init_ctx_iv(&ctx, key, iv); + + AES_CTR_xcrypt_buffer(&ctx, buffer, bufLen); +} + +uint32_t SecureApplicationLayer::calcAuthOnlyMac(uint8_t* apdu, uint8_t apduLength, const uint8_t* key, uint8_t* iv, uint8_t* ctr0) +{ + uint16_t bufLen = 2 + apduLength; // 2 bytes for the length field (uint16_t) + // AES-128 operates on blocks of 16 bytes, add padding + uint16_t bufLenPadded = (bufLen + 15) / 16 * 16; + uint8_t buffer[bufLenPadded]; + // Make sure to have zeroes everywhere, because of the padding + memset(buffer, 0x00, bufLenPadded); + + uint8_t* pBuf = buffer; + + pBuf = pushWord(apduLength, pBuf); + pBuf = pushByteArray(apdu, apduLength, pBuf); + + encryptAesCbc(buffer, bufLenPadded, iv, key); + xcryptAesCtr(buffer, 4, ctr0, key); // 4 bytes only for the MAC + + uint32_t mac; + popInt(mac, &buffer[0]); + + return mac; +} + +uint32_t SecureApplicationLayer::calcConfAuthMac(uint8_t* associatedData, uint16_t associatedDataLength, + uint8_t* apdu, uint8_t apduLength, + const uint8_t* key, uint8_t* iv) +{ + uint16_t bufLen = 2 + associatedDataLength + apduLength; // 2 bytes for the length field (uint16_t) + // AES-128 operates on blocks of 16 bytes, add padding + uint16_t bufLenPadded = (bufLen + 15) / 16 * 16; + uint8_t buffer[bufLenPadded]; + // Make sure to have zeroes everywhere, because of the padding + memset(buffer, 0x00, bufLenPadded); + + uint8_t* pBuf = buffer; + + pBuf = pushWord(associatedDataLength, pBuf); + pBuf = pushByteArray(associatedData, associatedDataLength, pBuf); + pBuf = pushByteArray(apdu, apduLength, pBuf); + + encryptAesCbc(buffer, bufLenPadded, iv, key); + + uint32_t mac; + popInt(mac, &buffer[bufLenPadded - 16]); // bufLenPadded has a guaranteed minimum size of 16 bytes + + return mac; +} + +void SecureApplicationLayer::block0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t extFrameFormat, uint8_t tpci, uint8_t apci, uint8_t payloadLength) +{ + uint8_t* pBuf = buffer; + pBuf = pushByteArray(seqNum, 6, pBuf); + pBuf = pushWord(indSrcAddr, pBuf); + pBuf = pushWord(dstAddr, pBuf); + pBuf = pushByte(0x00, pBuf); // FT: frametype + pBuf = pushByte( (dstAddrIsGroupAddr ? 0x80 : 0x00) | (extFrameFormat & 0xf), pBuf); // AT: address type + pBuf = pushByte(tpci, pBuf); // TPCI + pBuf = pushByte(apci, pBuf); // APCI // draft spec shows something different! + pBuf = pushByte(0x00, pBuf); // Reserved: fixed 0x00 (really?) + pBuf = pushByte(payloadLength, pBuf); // Payload length +} + +void SecureApplicationLayer::blockCtr0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr) +{ + uint8_t* pBuf = buffer; + pBuf = pushByteArray(seqNum, 6, pBuf); + pBuf = pushWord(indSrcAddr, pBuf); + pBuf = pushWord(dstAddr, pBuf); + pBuf = pushInt(0x00000000, pBuf); + pBuf = pushByte(0x01, pBuf); +} + +uint16_t SecureApplicationLayer::groupAddressIndex(uint16_t groupAddr) +{ + // Just for safety reasons, we should never get here, because the dataGroupIndication will return already return early without doing anything + if (_addrTab == nullptr) + return 0; + + return _addrTab->getTsap(groupAddr); +} + +const uint8_t* SecureApplicationLayer::securityKey(uint16_t addr, bool isGroupAddress) +{ + if (isGroupAddress) + { + uint16_t gaIndex = groupAddressIndex(addr); + + if (gaIndex > 0) + return _secIfObj.groupKey(gaIndex); + } + else + { + uint16_t iaIndex = _secIfObj.indAddressIndex(addr); + + if (iaIndex > 0) + return _secIfObj.p2pKey(iaIndex); + } + + return nullptr; +} + +// returns next outgoing sequence number for secure communication +uint64_t SecureApplicationLayer::nextSequenceNumber(bool toolAccess) +{ + return toolAccess ? _sequenceNumberToolAccess : _sequenceNumber; +} + +// stores next outgoing sequence number for secure communication +void SecureApplicationLayer::updateSequenceNumber(bool toolAccess, uint64_t seqNum) +{ + if (toolAccess) + { + _sequenceNumberToolAccess = seqNum; + } + else + { + _sequenceNumber = seqNum; + } + + // Also update the properties accordingly + _secIfObj.setSequenceNumber(toolAccess, seqNum); +} + +uint64_t SecureApplicationLayer::lastValidSequenceNumber(bool toolAccess, uint16_t srcAddr) +{ + if (toolAccess) + { + // TODO: check if we really have to support multiple tools at the same time + if (srcAddr == _deviceObj.individualAddress()) + return _sequenceNumberToolAccess; + + return _lastValidSequenceNumberTool; + } + else + { + return _secIfObj.getLastValidSequenceNumber(srcAddr); + } + + return 0; +} + +void SecureApplicationLayer::updateLastValidSequence(bool toolAccess, uint16_t remoteAddr, uint64_t seqNo) +{ + if (toolAccess) + // TODO: check if we really have to support multiple tools at the same time + _lastValidSequenceNumberTool = seqNo; + else + { + _secIfObj.setLastValidSequenceNumber(remoteAddr, seqNo); + } +} + +void SecureApplicationLayer::sendSyncRequest(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, bool systemBcast) +{ + if (secCtrl.dataSecurity != DataSecurity::AuthConf) + { + println("sync.req is always sent with auth+conf!"); + return; + } + + _syncReqBroadcastOutgoing = (dstAddr == 0x0000) && dstAddrIsGroupAddr; + + // use random number in SyncResponse + uint64_t challenge = getRandomNumber(); + + uint8_t asdu[6]; + sixBytesFromUInt64(challenge, &asdu[0]); + + CemiFrame request(2 + 6 + sizeof(asdu) + 4); // 2 bytes (APCI, SCF) + 6 bytes (SeqNum) + 6 bytes (challenge) + 4 bytes (MAC) + // Note: additional TPCI byte is already handled internally! + + uint8_t tpci = 0; + + if (!_syncReqBroadcastOutgoing) + { + if (isConnected()) + { + tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED) + } + } + + print("sendSyncRequest: TPCI: "); + println(tpci, HEX); + + if (secure(request.data() + APDU_LPDU_DIFF, kSecureSyncRequest, _deviceObj.individualAddress(), dstAddr, dstAddrIsGroupAddr, tpci, asdu, sizeof(asdu), secCtrl, systemBcast)) + { + println("SyncRequest: "); + request.apdu().printPDU(); + + if (_syncReqBroadcastOutgoing) + { + _transportLayer->dataBroadcastRequest(AckType::AckDontCare, HopCountType::NetworkLayerParameter, Priority::SystemPriority, request.apdu()); + } + else + { + // either send on T_DATA_INDIVIDUAL or T_DATA_CONNECTED + if (isConnected()) + { + _transportLayer->dataConnectedRequest(getConnectedTsasp(), SystemPriority, request.apdu()); + } + else + { + // Send encrypted SyncResponse message using T_DATA_INDIVIDUAL + _transportLayer->dataIndividualRequest(AckType::AckDontCare, NetworkLayerParameter, SystemPriority, dstAddr, request.apdu()); + } + } + + Addr toAddr = _syncReqBroadcastOutgoing ? (Addr)GrpAddr(0) : (Addr)IndAddr(dstAddr); + _pendingOutgoingSyncRequests.insertOrAssign(toAddr, challenge); + } + else + { + println("SyncRequest: failure during encryption"); + } +} + +void SecureApplicationLayer::sendSyncResponse(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, uint64_t remoteNextSeqNum, bool systemBcast) +{ + if (secCtrl.dataSecurity != DataSecurity::AuthConf) + { + println("sync.res is always sent with auth+conf!"); + return; + } + + uint64_t ourNextSeqNum = nextSequenceNumber(secCtrl.toolAccess); + + uint8_t asdu[12]; + sixBytesFromUInt64(ourNextSeqNum, &asdu[0]); + sixBytesFromUInt64(remoteNextSeqNum, &asdu[6]); + + CemiFrame response(2 + 6 + sizeof(asdu) + 4); // 2 bytes (APCI, SCF) + 6 bytes (SeqNum) + 12 bytes + 4 bytes (MAC) + // Note: additional TPCI byte is already handled internally! + + uint8_t tpci = 0; + + if (!_syncReqBroadcastIncoming) + { + if (isConnected()) + { + tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED) + } + } + + print("sendSyncResponse: TPCI: "); + println(tpci, HEX); + + if (secure(response.data() + APDU_LPDU_DIFF, kSecureSyncResponse, _deviceObj.individualAddress(), dstAddr, dstAddrIsGroupAddr, tpci, asdu, sizeof(asdu), secCtrl, systemBcast)) + { + _lastSyncRes = millis(); + + println("SyncResponse: "); + response.apdu().printPDU(); + + if (_syncReqBroadcastIncoming) + { + _transportLayer->dataBroadcastRequest(AckType::AckDontCare, HopCountType::NetworkLayerParameter, Priority::SystemPriority, response.apdu()); + } + else + { + // either send on T_DATA_INDIVIDUAL or T_DATA_CONNECTED + if (isConnected()) + { + _transportLayer->dataConnectedRequest(getConnectedTsasp(), SystemPriority, response.apdu()); + } + else + { + // Send encrypted SyncResponse message using T_DATA_INDIVIDUAL + _transportLayer->dataIndividualRequest(AckType::AckDontCare, NetworkLayerParameter, SystemPriority, dstAddr, response.apdu()); + } + } + } + else + { + println("SyncResponse: failure during encryption"); + } +} + +void SecureApplicationLayer::receivedSyncRequest(uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, uint8_t* seqNum, uint64_t challenge, bool systemBcast) +{ + println("Received SyncRequest:"); + + uint64_t nextRemoteSeqNum = sixBytesToUInt64(seqNum); + uint64_t nextSeqNum = 1 + lastValidSequenceNumber(secCtrl.toolAccess, srcAddr); + + if (nextRemoteSeqNum > nextSeqNum) + { + updateLastValidSequence(secCtrl.toolAccess, srcAddr, nextRemoteSeqNum - 1); + nextSeqNum = nextRemoteSeqNum; + } + + _syncReqBroadcastIncoming = (dstAddr == 0x0000) && dstAddrIsGroupAddr; + + // Remember challenge for securing the sync.res later + _pendingIncomingSyncRequests.insertOrAssign(_syncReqBroadcastIncoming ? (Addr) GrpAddr(0) : (Addr) IndAddr(srcAddr), challenge); + + uint16_t toAddr = _syncReqBroadcastIncoming ? dstAddr : srcAddr; + bool toIsGroupAddress = _syncReqBroadcastIncoming; + sendSyncResponse(toAddr, toIsGroupAddress, secCtrl, nextSeqNum, systemBcast); +} + +void SecureApplicationLayer::receivedSyncResponse(uint16_t remote, const SecurityControl& secCtrl, uint8_t* plainApdu) +{ + println("Received SyncResponse:"); + + if (_syncReqBroadcastOutgoing) + { + if (_pendingOutgoingSyncRequests.get(GrpAddr(0)) == nullptr) + { + println("Cannot handle sync.res without pending sync.req! (broadcast/systembroadcast)"); + return; + } + } + else + { + if (_pendingOutgoingSyncRequests.get(IndAddr(remote)) == nullptr) + { + println("Cannot handle sync.res without pending sync.req!"); + return; + } + } + + // Bytes 0-5 in the "APDU" buffer contain the remote sequence number + // Bytes 6-11 in the "APDU" buffer contain the local sequence number + uint64_t remoteSeq = sixBytesToUInt64(plainApdu + 0); + uint64_t localSeq = sixBytesToUInt64(plainApdu + 6); + + uint64_t last = lastValidSequenceNumber(secCtrl.toolAccess, remote); + + if (remoteSeq - 1 > last) + { + //logger.debug("sync.res update {} last valid {} seq -> {}", remote, toolAccess ? "tool access" : "p2p", remoteSeq -1); + updateLastValidSequence(secCtrl.toolAccess, remote, remoteSeq - 1); + } + + uint64_t next = nextSequenceNumber(secCtrl.toolAccess); + + if (localSeq > next) + { + //logger.debug("sync.res update local next {} seq -> {}", toolAccess ? "tool access" : "p2p", localSeq); + updateSequenceNumber(secCtrl.toolAccess, localSeq); + } + + Addr remoteAddr = _syncReqBroadcastOutgoing ? (Addr)GrpAddr(0) : (Addr)IndAddr(remote); + _pendingOutgoingSyncRequests.erase(remoteAddr); +} + +bool SecureApplicationLayer::decrypt(uint8_t* plainApdu, uint16_t plainApduLength, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci, uint8_t* secureAsdu, SecurityControl& secCtrl, bool systemBcast) +{ + const uint8_t* pBuf; + uint8_t scf; + + pBuf = popByte(scf, secureAsdu); + + bool toolAccess = ((scf & 0x80) == 0x80); + bool systemBroadcast = ((scf & 0x08) == 0x08); + uint8_t sai = (scf >> 4) & 0x07; // sai can only be 0x0 (CCM auth only) or 0x1 (CCM with auth+conf), other values are reserved + bool authOnly = ( sai == 0); + uint8_t service = (scf & 0x07); // only 0x0 (S-A_Data-PDU), 0x2 (S-A_Sync_Req-PDU) or 0x3 (S-A_Sync_Rsp-PDU) are valid values + + if (systemBroadcast != systemBcast) + { + println("SBC flag in SCF does not match actual communication mode!"); + } + + secCtrl.toolAccess = toolAccess; + secCtrl.dataSecurity = authOnly ? DataSecurity::Auth : DataSecurity::AuthConf; + + bool syncReq = service == kSecureSyncRequest; + bool syncRes = service == kSecureSyncResponse; + + //const uint8_t* key = dstAddrIsGroupAddr ? securityKey(dstAddr, dstAddrIsGroupAddr) : toolAccess ? toolKey() : securityKey(srcAddr, false); + const uint8_t* key = dstAddrIsGroupAddr && (dstAddr != 0) ? securityKey(dstAddr, dstAddrIsGroupAddr) : toolAccess ? _secIfObj.toolKey() : securityKey(srcAddr, false); + + if (key == nullptr) + { + print("Error: No key found. toolAccess: "); + println(toolAccess ? "true" : "false"); + return false; + } + + uint8_t seqNum[6]; + pBuf = popByteArray(seqNum, 6, pBuf); + uint64_t receivedSeqNumber = sixBytesToUInt64(seqNum); + + // Provide array for KNX serial number if it is a SyncRequest + // DataService and SyncResponse do not use this variable. + uint8_t knxSerialNumber[6]; + + uint16_t remainingPlainApduLength = plainApduLength; + + if (service == kSecureDataPdu) + { + if (srcAddr != _deviceObj.individualAddress()) + { + uint64_t expectedSeqNumber = lastValidSequenceNumber(toolAccess, srcAddr) + 1; + + if (receivedSeqNumber < expectedSeqNumber) + { + // security failure + print("security failure: received seqNum: "); + print(receivedSeqNumber, HEX); + print(" < expected seqNum: "); + print(expectedSeqNumber, HEX); + return false; + } + } + } + else if (syncReq) + { + pBuf = popByteArray(knxSerialNumber, 6, pBuf); + remainingPlainApduLength -= 6; + + // ignore sync.reqs not addressed to us + if (!memcmp(knxSerialNumber, _deviceObj.propertyData(PID_SERIAL_NUMBER), 6)) + { + uint8_t emptySerialNumber[6] = {0}; + + if (systemBroadcast || dstAddr != _deviceObj.individualAddress() || !memcmp(knxSerialNumber, emptySerialNumber, 6)) + return false; + } + + // if responded to another request within the last 1 second, ignore + if ((millis() - _lastSyncRes) < 1000) + { + return false; + } + } + else if (syncRes) + { + // fetch challenge depending on srcAddr to handle multiple requests + uint64_t* challenge = _pendingOutgoingSyncRequests.get(IndAddr(srcAddr)); + + if (challenge == nullptr) + { + println("Cannot find matching challenge for source address!"); + return false; + } + + uint8_t _challengeSixBytes[6]; + sixBytesFromUInt64(*challenge, _challengeSixBytes); + + // in a sync.res, seq actually contains our challenge from sync.req xored with a random value + // extract the random value and store it in seqNum to use it for block0 and ctr0 + for (uint8_t i = 0; i < sizeof(seqNum); i++) + { + seqNum[i] ^= _challengeSixBytes[i]; + } + } + + pBuf = popByteArray(plainApdu, remainingPlainApduLength, pBuf); + + // No LTE-HEE for now + // Data Secure always uses extended frame format with APDU length > 15 octets (long messages), no standard frames + uint8_t extendedFrameFormat = 0; + // Clear IV buffer + uint8_t iv[16] = {0x00}; + // Create first block B0 for AES CBC MAC calculation, used as IV later + /* + printHex("seq: ", seqNum, 6); + printHex("src: ", (uint8_t*) &srcAddr, 2); + printHex("dst: ", (uint8_t*) &dstAddr, 2); + print("dstAddrisGroup: ");println(dstAddrIsGroupAddr ? "true" : "false"); + */ + block0(iv, seqNum, srcAddr, dstAddr, dstAddrIsGroupAddr, extendedFrameFormat, tpci | (SecureService >> 8), SecureService & 0x00FF, remainingPlainApduLength); + + // Clear block counter0 buffer + uint8_t ctr0[16] = {0x00}; + // Create first block for block counter 0 + blockCtr0(ctr0, seqNum, srcAddr, dstAddr); + + uint32_t mac; + pBuf = popInt(mac, pBuf); + + if (authOnly) + { + // APDU is already plain, no decryption needed + + // Only check the MAC + uint32_t calculatedMac = calcAuthOnlyMac(plainApdu, remainingPlainApduLength, key, iv, ctr0); + + if (calculatedMac != mac) + { + // security failure + print("security failure(auth): calculated MAC: "); + print(calculatedMac, HEX); + print(" != received MAC: "); + print(mac, HEX); + println("\n"); + + return false; + } + + memcpy(plainApdu, secureAsdu, remainingPlainApduLength); + } + else + { + // APDU is encrypted and needs decryption + + uint16_t bufLen = 4 + remainingPlainApduLength; + // AES-128 operates on blocks of 16 bytes, add padding + uint16_t bufLenPadded = (bufLen + 15) / 16 * 16; + uint8_t buffer[bufLenPadded]; + //uint8_t buffer[bufLen]; + // Make sure to have zeroes everywhere, because of the padding + memset(buffer, 0x00, bufLenPadded); + + pushInt(mac, &buffer[0]); + pushByteArray(plainApdu, remainingPlainApduLength, &buffer[4]); // apdu is still encrypted + + xcryptAesCtr(buffer, bufLenPadded, ctr0, key); + //xcryptAesCtr(buffer, bufLen, ctr0, key); + + uint32_t decryptedMac; + popInt(decryptedMac, &buffer[0]); + popByteArray(plainApdu, remainingPlainApduLength, &buffer[4]); // apdu is now decrypted (overwritten) + + // Do calculations for Auth+Conf + uint8_t associatedData[syncReq ? 7 : 1]; + associatedData[0] = scf; + + if (syncReq) + { + memcpy(&associatedData[1], knxSerialNumber, 6); + } + + /* + printHex("APDU--------->", plainApdu, remainingPlainApduLength); + printHex("Key---------->", key, 16); + printHex("ASSOC-------->", associatedData, sizeof(associatedData)); + */ + uint32_t calculatedMac = calcConfAuthMac(associatedData, sizeof(associatedData), plainApdu, remainingPlainApduLength, key, iv); + + if (calculatedMac != decryptedMac) + { + // security failure + print("security failure(conf+auth): calculated MAC: "); + print(calculatedMac, HEX); + print(" != decrypted MAC: "); + print(decryptedMac, HEX); + println("\n"); + + return false; + } + + // prevent a sync.req sent by us to trigger sync notification, this happens if we provide our own tool key + // for decryption above + if (syncReq && (srcAddr == _deviceObj.individualAddress())) + return false; + + if (syncReq) + { + uint64_t challenge = sixBytesToUInt64(&plainApdu[0]); + receivedSyncRequest(srcAddr, dstAddr, dstAddrIsGroupAddr, secCtrl, seqNum, challenge, systemBroadcast); + return false; + } + else if (syncRes) + { + receivedSyncResponse(srcAddr, secCtrl, plainApdu); + return false; + } + else + { + if (srcAddr == _deviceObj.individualAddress()) + { + print("Update our next "); + print(toolAccess ? "tool access" : ""); + print(" seq from "); + print(srcAddr, HEX); + print(" -> (+1) "); + println(receivedSeqNumber, HEX); + updateSequenceNumber(toolAccess, receivedSeqNumber + 1); + } + else + { + print("Update last valid "); + print(toolAccess ? "tool access" : ""); + print(" seq from "); + print(srcAddr, HEX); + print(" -> "); + println(receivedSeqNumber, HEX); + updateLastValidSequence(toolAccess, srcAddr, receivedSeqNumber); + } + } + } + + return true; +} + +bool SecureApplicationLayer::decodeSecureApdu(APDU& secureApdu, APDU& plainApdu, SecurityControl& secCtrl) +{ + // Decode secure APDU + + println("decodeSecureApdu: Secure APDU: "); + secureApdu.printPDU(); + + uint16_t srcAddress = secureApdu.frame().sourceAddress(); + uint16_t dstAddress = secureApdu.frame().destinationAddress(); + bool isDstAddrGroupAddr = secureApdu.frame().addressType() == GroupAddress; + bool isSystemBroadcast = secureApdu.frame().systemBroadcast(); + uint8_t tpci = secureApdu.frame().data()[TPDU_LPDU_DIFF]; // FIXME: when cEMI class is refactored, there might be additional info fields in cEMI [fixed TPDU_LPDU_DIFF] + print("decodeSecureApdu: TPCI: "); + println(tpci, HEX); + // Note: + // The TPCI is also included in the MAC calculation to provide authenticity for this field. + // However, a secure APDU (with a MAC) is only included in transport layer PDUs T_DATA_GROUP, T_DATA_TAG_GROUP, T_DATA_INDIVIDUAL, T_DATA_CONNECTED + // and not in T_CONNECT, T_DISCONNECT, T_ACK, T_NACK. + // This means the DATA/CONTROL flag is always 0(=DATA). The flag "NUMBERED" differentiates between T_DATA_INDIVIDUAL and T_DATA_CONNECTED. + // The seqNumber is only used in T_DATA_CONNECTED and 0 in case of T_DATA_GROUP and T_DATA_GROUP (leaving out T_DATA_TAG_GROUP). + // Summary: effectively only the "NUMBERED" flag (bit6) and the SeqNumber (bit5-2) are used from transport layer. + // In T_DATA_* services the bits1-0 of TPCI octet are used as bits9-8 for APCI type which is fixed to 0x03. SecureService APCI is 0x03F1. + + // FIXME: when cEMI class is refactored, there might be additional info fields in cEMI (fixed APDU_LPDU_DIFF) + // We are starting from TPCI octet (including): plainApdu.frame().data()+APDU_LPDU_DIFF + if (decrypt(plainApdu.frame().data() + APDU_LPDU_DIFF, plainApdu.length() + 1, srcAddress, dstAddress, isDstAddrGroupAddr, tpci, secureApdu.data() + 1, secCtrl, isSystemBroadcast)) + { + println("decodeSecureApdu: Plain APDU: "); + plainApdu.frame().apdu().printPDU(); + + return true; + } + + return false; +} + +bool SecureApplicationLayer::secure(uint8_t* buffer, uint16_t service, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci, + uint8_t* apdu, uint16_t apduLength, const SecurityControl& secCtrl, bool systemBcast) +{ + bool toolAccess = secCtrl.toolAccess; + bool confidentiality = secCtrl.dataSecurity == DataSecurity::AuthConf; + + if (toolAccess) + { + if (!confidentiality) + { + println("Error: tool access requires auth+conf security"); + return false; + } + + if (dstAddrIsGroupAddr && dstAddr != 0) + { + println("Error: tool access requires individual address"); + return false; + } + } + + const uint8_t* key = toolAccess ? _secIfObj.toolKey() : securityKey(dstAddr, dstAddrIsGroupAddr); + + if (key == nullptr) + { + print("Error: No key found. toolAccess: "); + println(toolAccess ? "true" : "false"); + return false; + } + + bool syncReq = service == kSecureSyncRequest; + bool syncRes = service == kSecureSyncResponse; + + tpci |= SecureService >> 8; // OR'ing upper two APCI bits + uint8_t apci = SecureService & 0x00FF; + uint8_t* pBuf = buffer; + pBuf = pushByte(tpci, pBuf); // TPCI + pBuf = pushByte(apci, pBuf); // APCI + + uint8_t scf; + scf = service; + scf |= toolAccess ? 0x80 : 0; + scf |= confidentiality ? 0x10 : 0; + scf |= systemBcast ? 0x8 : 0; + + pBuf = pushByte(scf, pBuf); // SCF + + uint64_t seqSend = nextSequenceNumber(toolAccess); + + if (seqSend == 0) + println("0 is not a valid sequence number"); + + uint8_t seq[6]; + sixBytesFromUInt64(seqSend, seq); + + if (!syncRes) + pBuf = pushByteArray(seq, 6, pBuf); // Sequence Number + + // Prepare associated data depending on service (SyncRequest, SyncResponse or just DataService) + uint8_t associatedData[syncReq ? 7 : 1]; + associatedData[0] = scf; + + if (syncReq) + { + // TODO: NYI lookup serial number of target device for SBC sync.req + uint8_t remoteSerialNo[6] = {0}; + + uint8_t emptySerialNo[6] = {0}; + pBuf = pushByteArray(systemBcast ? remoteSerialNo : emptySerialNo, 6, pBuf); + pushByteArray(_deviceObj.propertyData(PID_SERIAL_NUMBER), 6, &associatedData[1]); + } + else if (syncRes) + { + // use random number in SyncResponse + uint64_t randomNumber = getRandomNumber(); + sixBytesFromUInt64(randomNumber, seq); + + Addr remote = _syncReqBroadcastIncoming ? (Addr)GrpAddr(0) : (Addr)IndAddr(dstAddr); + + // Get challenge from sync.req + uint64_t* challenge = _pendingIncomingSyncRequests.get(remote); + + if (challenge == nullptr) + { + println("Cannot send sync.res without corresponding sync.req"); + return false; + } + else + { + _pendingIncomingSyncRequests.erase(remote); + } + + uint8_t challengeSixBytes[6]; + sixBytesFromUInt64(*challenge, challengeSixBytes); + //printHex("Decrypted challenge: ", challengeSixBytes, 6); + + // Now XOR the new random SeqNum with the challenge from the SyncRequest + uint8_t rndXorChallenge[6]; + pushByteArray(seq, 6, rndXorChallenge); + + for (uint8_t i = 0; i < sizeof(rndXorChallenge); i++) + { + rndXorChallenge[i] ^= challengeSixBytes[i]; + } + + pBuf = pushByteArray(rndXorChallenge, 6, pBuf); + } + + // No LTE-HEE for now + // Data Secure always uses extended frame format with APDU length > 15 octets (long messages), no standard frames + uint8_t extendedFrameFormat = 0; + // Clear IV buffer + uint8_t iv[16] = {0x00}; + // Create first block B0 for AES CBC MAC calculation, used as IV later + /* + printHex("seq: ", seq, 6); + printHex("src: ", (uint8_t*) &srcAddr, 2); + printHex("dst: ", (uint8_t*) &dstAddr, 2); + print("dstAddrisGroup: ");println(dstAddrIsGroupAddr ? "true" : "false"); + */ + block0(iv, seq, srcAddr, dstAddr, dstAddrIsGroupAddr, extendedFrameFormat, tpci, apci, apduLength); + + // Clear block counter0 buffer + uint8_t ctr0[16] = {0x00}; + // Create first block for block counter 0 + blockCtr0(ctr0, seq, srcAddr, dstAddr); + + if (confidentiality) + { + // Do calculations for Auth+Conf + /* + printHex("APDU--------->", apdu, apduLength); + printHex("Key---------->", key, 16); + printHex("ASSOC-------->", associatedData, sizeof(associatedData)); + */ + uint32_t mac = calcConfAuthMac(associatedData, sizeof(associatedData), apdu, apduLength, key, iv); + + uint8_t tmpBuffer[4 + apduLength]; + pushInt(mac, tmpBuffer); + pushByteArray(apdu, apduLength, &tmpBuffer[4]); + + xcryptAesCtr(tmpBuffer, apduLength + 4, ctr0, key); // APDU + MAC (4 bytes) + + pBuf = pushByteArray(tmpBuffer + 4, apduLength, pBuf); // Encrypted APDU + pBuf = pushByteArray(tmpBuffer + 0, 4, pBuf); // Encrypted MAC + + //print("MAC(encrypted): "); + //println(*((uint32_t*)(tmpBuffer + 0)),HEX); + } + else + { + // Do calculations for AuthOnly + uint32_t tmpMac = calcAuthOnlyMac(apdu, apduLength, key, iv, ctr0); + + pBuf = pushByteArray(apdu, apduLength, pBuf); // Plain APDU + pBuf = pushInt(tmpMac, pBuf); // MAC + + print("MAC: "); + println(tmpMac, HEX); + } + + return true; +} + +bool SecureApplicationLayer::createSecureApdu(APDU& plainApdu, APDU& secureApdu, const SecurityControl& secCtrl) +{ + // Create secure APDU + + println("createSecureApdu: Plain APDU: "); + plainApdu.printPDU(); + + uint16_t srcAddress = plainApdu.frame().sourceAddress(); + uint16_t dstAddress = plainApdu.frame().destinationAddress(); + bool isDstAddrGroupAddr = plainApdu.frame().addressType() == GroupAddress; + bool isSystemBroadcast = plainApdu.frame().systemBroadcast(); + uint8_t tpci = 0x00; + + if (isConnected()) + { + tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED) + } + + print("createSecureApdu: TPCI: "); + println(tpci, HEX); + // Note: + // The TPCI is also included in the MAC calculation to provide authenticity for this field. + // However, a secure APDU (with a MAC) is only included in transport layer PDUs T_DATA_GROUP, T_DATA_TAG_GROUP, T_DATA_INDIVIDUAL, T_DATA_CONNECTED + // and not in T_CONNECT, T_DISCONNECT, T_ACK, T_NACK. + // This means the DATA/CONTROL flag is always 0(=DATA). The flag "NUMBERED" differentiates between T_DATA_INDIVIDUAL and T_DATA_CONNECTED. + // The seqNumber is only used in T_DATA_CONNECTED and 0 in case of T_DATA_GROUP and T_DATA_GROUP (leaving out T_DATA_TAG_GROUP). + // Summary: effectively only the "NUMBERED" flag (bit6) and the SeqNumber (bit5-2) are used from transport layer. + // In T_DATA_* services the bits1-0 of TPCI octet are used as bits9-8 for APCI type which is fixed to 0x03. SecureService APCI is 0x03F1. + + // FIXME: when cEMI class is refactored, there might be additional info fields in cEMI (fixed APDU_LPDU_DIFF) + // We are starting from TPCI octet (including): plainApdu.frame().data()+APDU_LPDU_DIFF + if (secure(secureApdu.frame().data() + APDU_LPDU_DIFF, kSecureDataPdu, srcAddress, dstAddress, isDstAddrGroupAddr, tpci, plainApdu.frame().data() + APDU_LPDU_DIFF, plainApdu.length() + 1, secCtrl, isSystemBroadcast)) + { + print("Update our next "); + print(secCtrl.toolAccess ? "tool access" : ""); + print(" seq from "); + print(srcAddress, HEX); + print(" -> (+1) "); + println(nextSequenceNumber(secCtrl.toolAccess), HEX); + updateSequenceNumber(secCtrl.toolAccess, nextSequenceNumber(secCtrl.toolAccess) + 1); + + println("createSecureApdu: Secure APDU: "); + secureApdu.frame().apdu().printPDU(); + + return true; + } + + return false; +} + +uint64_t SecureApplicationLayer::getRandomNumber() +{ + return 0x000102030405; // TODO: generate random number +} + +void SecureApplicationLayer::loop() +{ + // TODO: handle timeout of outgoing sync requests + //_pendingOutgoingSyncRequests +} + +bool SecureApplicationLayer::isSyncService(APDU& secureApdu) +{ + uint8_t scf = *(secureApdu.data() + 1); + uint8_t service = (scf & 0x07); // only 0x0 (S-A_Data-PDU), 0x2 (S-A_Sync_Req-PDU) or 0x3 (S-A_Sync_Rsp-PDU) are valid values + + if ((service == kSecureSyncRequest) || (service == kSecureSyncResponse)) + { + return true; + } + + return false; +} +#endif diff --git a/components/knx/src/knx/secure_application_layer.h b/components/knx/src/knx/secure_application_layer.h new file mode 100644 index 0000000..d89d82f --- /dev/null +++ b/components/knx/src/knx/secure_application_layer.h @@ -0,0 +1,162 @@ +#pragma once + +#include "application_layer.h" +#include +#include "knx_types.h" +#include "apdu.h" +#include "bits.h" +#include "simple_map.h" + +class DeviceObject; +class SecurityInterfaceObject; +class AssociationTableObject; +class AddressTableObject; +class BusAccessUnit; +/** + * This is an implementation of the application layer as specified in @cite knx:3/5/1. + * It provides methods for the BusAccessUnit to do different things and translates this + * call to an APDU and calls the correct method of the TransportLayer. + * It also takes calls from TransportLayer, decodes the submitted APDU and calls the coresponding + * methods of the BusAccessUnit class. + */ +class SecureApplicationLayer : public ApplicationLayer +{ + public: + /** + * The constructor. + * @param assocTable The AssociationTable is used to translate between asap (i.e. group objects) and group addresses. + * @param bau methods are called here depending of the content of the APDU + */ + SecureApplicationLayer(DeviceObject& deviceObj, SecurityInterfaceObject& secIfObj, BusAccessUnit& bau); + + void groupAddressTable(AddressTableObject& addrTable); + + // from transport layer + void dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu) override; + void dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, + APDU& apdu, bool status) override; + void dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) override; + void dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, bool status) override; + void dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) override; + void dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status) override; + void dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu) override; + void dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status) override; + void dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu) override; + void dataConnectedConfirm(uint16_t tsap) override; + + void loop(); + + protected: + // to transport layer + void dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl) override; + void dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl) override; + void dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl) override; + void dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu, const SecurityControl& secCtrl) override; + void dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu, const SecurityControl& secCtrl) override; // apdu must be valid until it was confirmed + + private: + enum class AddrType : uint8_t + { + group, + individual, + unknown + }; + + struct Addr + { + Addr() = default; + Addr(uint8_t addr) : addr{addr} {} + + uint16_t addr; + AddrType addrType{AddrType::unknown}; + + bool operator ==(const Addr& cmpAddr) const + { + if ((cmpAddr.addrType == AddrType::unknown) || (addrType == AddrType::unknown)) + { + println("Unknown address type detected!"); + return false; + } + + return (cmpAddr.addr == addr) && (cmpAddr.addrType == addrType); + } + }; + + struct GrpAddr : Addr + { + GrpAddr() + { + addrType = AddrType::group; + } + GrpAddr(uint8_t addr) : Addr{addr} + { + addrType = AddrType::group; + } + }; + + struct IndAddr : Addr + { + IndAddr() + { + addrType = AddrType::individual; + } + IndAddr(uint8_t addr) : Addr{addr} + { + addrType = AddrType::individual; + } + }; + + uint32_t calcAuthOnlyMac(uint8_t* apdu, uint8_t apduLength, const uint8_t* key, uint8_t* iv, uint8_t* ctr0); + uint32_t calcConfAuthMac(uint8_t* associatedData, uint16_t associatedDataLength, uint8_t* apdu, uint8_t apduLength, const uint8_t* key, uint8_t* iv); + + void block0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t extFrameFormat, uint8_t tpci, uint8_t apci, uint8_t payloadLength); + void blockCtr0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr); + + const uint8_t* securityKey(uint16_t addr, bool isGroupAddress); + + uint16_t groupAddressIndex(uint16_t groupAddr); // returns 1-based index of address in group address table + uint16_t groupObjectIndex(uint16_t groupAddrIndex); // returns 1-based index of object in association table + + uint8_t groupObjectSecurity(uint16_t groupObjectIndex); + + uint64_t nextSequenceNumber(bool toolAccess); + void updateSequenceNumber(bool toolAccess, uint64_t seqNum); + + uint64_t lastValidSequenceNumber(bool toolAcces, uint16_t srcAddr); + void updateLastValidSequence(bool toolAccess, uint16_t remoteAddr, uint64_t seqNo); + + uint64_t getRandomNumber(); + + bool isSyncService(APDU& secureAsdu); + + void sendSyncRequest(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, bool systemBcast); + void sendSyncResponse(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, uint64_t remoteNextSeqNum, bool systemBcast); + void receivedSyncRequest(uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, uint8_t* seq, uint64_t challenge, bool systemBcast); + void receivedSyncResponse(uint16_t remoteAddr, const SecurityControl& secCtrl, uint8_t* plainApdu); + + bool decrypt(uint8_t* plainApdu, uint16_t plainapduLength, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci, uint8_t* secureAsdu, SecurityControl& secCtrl, bool systemBcast); + bool secure(uint8_t* buffer, uint16_t service, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci, uint8_t* apdu, uint16_t apduLength, const SecurityControl& secCtrl, bool systemBcast); + + bool decodeSecureApdu(APDU& secureApdu, APDU& plainApdu, SecurityControl& secCtrl); + bool createSecureApdu(APDU& plainApdu, APDU& secureApdu, const SecurityControl& secCtrl); + + void encryptAesCbc(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key); + void xcryptAesCtr(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key); + + bool _syncReqBroadcastIncoming{false}; + bool _syncReqBroadcastOutgoing{false}; + uint32_t _lastSyncRes; + + Map _pendingOutgoingSyncRequests; // Store challenges for outgoing sync requests + Map _pendingIncomingSyncRequests; // Store challenges for incoming sync requests + + uint64_t _sequenceNumberToolAccess = 50; + uint64_t _sequenceNumber = 0; + + uint64_t _lastValidSequenceNumberTool = 0; + uint64_t _lastValidSequenceNumber = 0; + + SecurityInterfaceObject& _secIfObj; + DeviceObject& _deviceObj; + AddressTableObject* _addrTab = nullptr; +}; diff --git a/components/knx/src/knx/security_interface_object.cpp b/components/knx/src/knx/security_interface_object.cpp new file mode 100644 index 0000000..69e9558 --- /dev/null +++ b/components/knx/src/knx/security_interface_object.cpp @@ -0,0 +1,605 @@ +#include "config.h" +#ifdef USE_DATASECURE + +#include +#include "security_interface_object.h" +#include "secure_application_layer.h" +#include "bits.h" +#include "data_property.h" +#include "callback_property.h" +#include "function_property.h" + +// Our FDSK. It is never changed from ETS. This is the permanent default tool key that is restored on every factory reset of the device. +const uint8_t SecurityInterfaceObject::_fdsk[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; +uint8_t SecurityInterfaceObject::_secReport[] = { 0x00, 0x00, 0x00 }; +uint8_t SecurityInterfaceObject::_secReportCtrl[] = { 0x00, 0x00, 0x00 }; + +SecurityInterfaceObject::SecurityInterfaceObject() +{ + Property* properties[] = + { + new DataProperty( PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0, (uint16_t)OT_SECURITY ), + new CallbackProperty(this, PID_LOAD_STATE_CONTROL, true, PDT_CONTROL, 1, ReadLv3 | WriteLv3, + // ReadCallback of PID_LOAD_STATE_CONTROL + [](SecurityInterfaceObject * obj, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t { + if (start == 0) + return 1; + + data[0] = obj->_state; + return 1; + }, + // WriteCallback of PID_LOAD_STATE_CONTROL + [](SecurityInterfaceObject * obj, uint16_t start, uint8_t count, const uint8_t* data) -> uint8_t { + obj->loadEvent(data); + return 1; + }), + new FunctionProperty(this, PID_SECURITY_MODE, + // Command Callback of PID_SECURITY_MODE + [](SecurityInterfaceObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void { + uint8_t serviceId = data[1] & 0xff; + + if (serviceId != 0) + { + resultData[0] + = ReturnCodes::InvalidCommand; + resultLength = 1; + return; + } + if (length == 3) + { + uint8_t mode = data[2]; + + if (mode > 1) + { + resultData[0] = ReturnCodes::DataVoid; + resultLength = 1; + return; + } + + obj->setSecurityMode(mode == 1); + resultData[0] = ReturnCodes::Success; + resultData[1] = serviceId; + resultLength = 2; + return; + } + resultData[0] = ReturnCodes::GenericError; + resultLength = 1; + }, + // State Callback of PID_SECURITY_MODE + [](SecurityInterfaceObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void { + uint8_t serviceId = data[1] & 0xff; + + if (serviceId != 0) + { + resultData[0] + = ReturnCodes::InvalidCommand; + resultLength = 1; + return; + } + if (length == 2) + { + resultData[0] + = ReturnCodes::Success; + resultData[1] = serviceId; + resultData[2] = obj->isSecurityModeEnabled() ? 1 : 0; + resultLength = 3; + return; + } + resultData[0] = ReturnCodes::GenericError; + resultLength = 1; + }), + new DataProperty( PID_P2P_KEY_TABLE, true, PDT_GENERIC_20, 1, ReadLv3 | WriteLv0 ), // written by ETS + new DataProperty( PID_GRP_KEY_TABLE, true, PDT_GENERIC_18, 50, ReadLv3 | WriteLv0 ), // written by ETS + new DataProperty( PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE, true, PDT_GENERIC_08, 32, ReadLv3 | WriteLv0 ), // written by ETS + new FunctionProperty(this, PID_SECURITY_FAILURES_LOG, + // Command Callback of PID_SECURITY_FAILURES_LOG + [](SecurityInterfaceObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void { + if (length != 3) + { + resultData[0] + = ReturnCodes::DataVoid; + resultLength = 1; + return; + } + uint8_t id = data[1]; + uint8_t info = data[2]; + + if (id == 0 && info == 0) + { + obj->clearFailureLog(); + resultData[0] = ReturnCodes::Success; + resultData[1] = id; + resultLength = 2; + return; + } + resultData[0] = ReturnCodes::GenericError; + resultLength = 1; + }, + // State Callback of PID_SECURITY_FAILURES_LOG + [](SecurityInterfaceObject * obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void { + if (length != 3) + { + resultData[0] + = ReturnCodes::DataVoid; + resultLength = 1; + return; + } + uint8_t id = data[1]; + uint8_t info = data[2]; + + // failure counters + if (id == 0 && info == 0) + { + resultData[0] + = ReturnCodes::Success; + resultData[1] = id; + resultData[2] = info; + obj->getFailureCounters(&resultData[3]); // Put 8 bytes in the buffer + resultLength = 3 + 8; + return; + } + // query latest failure by index + else if (id == 1) + { + uint8_t maxBufferSize = resultLength; // Remember the maximum buffer size of the buffer that is provided to us + uint8_t index = info; + uint8_t numBytes = obj->getFromFailureLogByIndex(index, &resultData[2], maxBufferSize); + + if ( numBytes > 0) + { + resultData[0] = ReturnCodes::Success; + resultData[1] = id; + resultData[2] = index; + resultLength += numBytes; + resultLength = 3 + numBytes; + return; + } + + resultData[0] = ReturnCodes::DataVoid; + resultData[1] = id; + resultLength = 2; + return; + } + resultData[0] = ReturnCodes::GenericError; + resultLength = 1; + }), + new DataProperty( PID_TOOL_KEY, true, PDT_GENERIC_16, 1, ReadLv3 | WriteLv0, (uint8_t*) _fdsk ), // default is FDSK // ETS changes this property during programming from FDSK to some random key! + new DataProperty( PID_SECURITY_REPORT, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, _secReport ), // Not implemented + new DataProperty( PID_SECURITY_REPORT_CONTROL, true, PDT_BINARY_INFORMATION, 1, ReadLv3 | WriteLv0, _secReportCtrl ), // Not implemented + new DataProperty( PID_SEQUENCE_NUMBER_SENDING, true, PDT_GENERIC_06, 1, ReadLv3 | WriteLv0 ), // Updated by our device accordingly + new DataProperty( PID_ZONE_KEY_TABLE, true, PDT_GENERIC_19, 1, ReadLv3 | WriteLv0 ), // written by ETS + new DataProperty( PID_GO_SECURITY_FLAGS, true, PDT_GENERIC_01, 256, ReadLv3 | WriteLv0 ), // written by ETS + new DataProperty( PID_ROLE_TABLE, true, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0 ), // written by ETS + new DataProperty( PID_ERROR_CODE, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0, (uint8_t)E_NO_FAULT), + new DataProperty( PID_TOOL_SEQUENCE_NUMBER_SENDING, true, PDT_GENERIC_06, 1, ReadLv3 | WriteLv0 ) // Updated by our device accordingly (non-standardized!) + }; + initializeProperties(sizeof(properties), properties); +} + +uint8_t* SecurityInterfaceObject::save(uint8_t* buffer) +{ + buffer = pushByte(_state, buffer); + buffer = pushByte(_securityModeEnabled, buffer); + + return InterfaceObject::save(buffer); +} + +const uint8_t* SecurityInterfaceObject::restore(const uint8_t* buffer) +{ + uint8_t state = 0; + buffer = popByte(state, buffer); + _state = (LoadState)state; + + uint8_t securityModeEnabled = 0; + buffer = popByte(securityModeEnabled, buffer); + _securityModeEnabled = securityModeEnabled; + + return InterfaceObject::restore(buffer); +} + +uint16_t SecurityInterfaceObject::saveSize() +{ + return 2 + InterfaceObject::saveSize(); +} + +void SecurityInterfaceObject::setSecurityMode(bool enabled) +{ + print("Security mode set to: "); + println(enabled ? "enabled" : "disabled"); + _securityModeEnabled = enabled; +} + +bool SecurityInterfaceObject::isSecurityModeEnabled() +{ + return _securityModeEnabled; +} + +void SecurityInterfaceObject::clearFailureLog() +{ + println("clearFailureLog()"); +} + +void SecurityInterfaceObject::getFailureCounters(uint8_t* data) +{ + memset(data, 0, 8); + println("getFailureCounters()"); +} + +uint8_t SecurityInterfaceObject::getFromFailureLogByIndex(uint8_t index, uint8_t* data, uint8_t maxDataLen) +{ + print("getFromFailureLogByIndex(): Index: "); + println(index); + return 0; +} + +bool SecurityInterfaceObject::isLoaded() +{ + return _state == LS_LOADED; +} + +LoadState SecurityInterfaceObject::loadState() +{ + return _state; +} + +void SecurityInterfaceObject::loadEvent(const uint8_t* data) +{ + switch (_state) + { + case LS_UNLOADED: + loadEventUnloaded(data); + break; + + case LS_LOADING: + loadEventLoading(data); + break; + + case LS_LOADED: + loadEventLoaded(data); + break; + + case LS_ERROR: + loadEventError(data); + break; + + default: + /* do nothing */ + break; + } +} + +void SecurityInterfaceObject::loadEventUnloaded(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_LOAD_COMPLETED: + case LE_ADDITIONAL_LOAD_CONTROLS: + case LE_UNLOAD: + break; + + case LE_START_LOADING: + loadState(LS_LOADING); + break; + + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void SecurityInterfaceObject::loadEventLoading(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_START_LOADING: + break; + + case LE_LOAD_COMPLETED: + loadState(LS_LOADED); + break; + + case LE_UNLOAD: + loadState(LS_UNLOADED); + break; + + case LE_ADDITIONAL_LOAD_CONTROLS: // Not supported here + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void SecurityInterfaceObject::loadEventLoaded(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_LOAD_COMPLETED: + break; + + case LE_START_LOADING: + loadState(LS_LOADING); + break; + + case LE_UNLOAD: + loadState(LS_UNLOADED); + break; + + case LE_ADDITIONAL_LOAD_CONTROLS: + loadState(LS_ERROR); + errorCode(E_INVALID_OPCODE); + break; + + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void SecurityInterfaceObject::loadEventError(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_LOAD_COMPLETED: + case LE_ADDITIONAL_LOAD_CONTROLS: + case LE_START_LOADING: + break; + + case LE_UNLOAD: + loadState(LS_UNLOADED); + break; + + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void SecurityInterfaceObject::loadState(LoadState newState) +{ + if (newState == _state) + return; + + //beforeStateChange(newState); + _state = newState; +} + +void SecurityInterfaceObject::errorCode(ErrorCode errorCode) +{ + uint8_t data = errorCode; + Property* prop = property(PID_ERROR_CODE); + prop->write(data); +} + +void SecurityInterfaceObject::masterReset(EraseCode eraseCode, uint8_t channel) +{ + if (eraseCode == FactoryReset) + { + // TODO handle different erase codes + println("Factory reset of security interface object requested."); + setSecurityMode(false); + property(PID_TOOL_KEY)->write(1, 1, _fdsk); + } +} + +const uint8_t* SecurityInterfaceObject::toolKey() +{ + // There is only one tool key + const uint8_t* toolKey = propertyData(PID_TOOL_KEY); + return toolKey; +} + +const uint8_t* SecurityInterfaceObject::p2pKey(uint16_t addressIndex) +{ + if (!isLoaded()) + return nullptr; + + // Get number of entries for this property + uint16_t numElements = getNumberOfElements(PID_P2P_KEY_TABLE); + + if (numElements > 0) + { + uint8_t elementSize = propertySize(PID_P2P_KEY_TABLE); + + // Search for address index + uint8_t entry[elementSize]; // 2 bytes index + keysize (16 bytes) + 2 bytes(roles) = 20 bytes + + for (int i = 1; i <= numElements; i++) + { + property(PID_P2P_KEY_TABLE)->read(i, 1, entry); + uint16_t index = (entry[0] << 8) | entry[1]; + + if (index > addressIndex) + { + return nullptr; + } + + if (index == addressIndex) + { + return propertyData(PID_P2P_KEY_TABLE, i) + sizeof(index); + } + } + } + + return nullptr; +} + +const uint8_t* SecurityInterfaceObject::groupKey(uint16_t addressIndex) +{ + if (!isLoaded()) + return nullptr; + + // Get number of entries for this property + uint16_t numElements = getNumberOfElements(PID_GRP_KEY_TABLE); + + if (numElements > 0) + { + uint8_t elementSize = propertySize(PID_GRP_KEY_TABLE); + + // Search for address index + uint8_t entry[elementSize]; // 2 bytes index + keysize (16 bytes) = 18 bytes + + for (int i = 1; i <= numElements; i++) + { + property(PID_GRP_KEY_TABLE)->read(i, 1, entry); + uint16_t index = ((entry[0] << 8) | entry[1]); + + if (index > addressIndex) + { + return nullptr; + } + + if (index == addressIndex) + { + return propertyData(PID_GRP_KEY_TABLE, i) + sizeof(index); + } + } + } + + return nullptr; +} + +uint16_t SecurityInterfaceObject::indAddressIndex(uint16_t indAddr) +{ + // Get number of entries for this property + uint16_t numElements = getNumberOfElements(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE); + + if (numElements > 0) + { + uint8_t elementSize = propertySize(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE); + + // Search for individual address + uint8_t entry[elementSize]; // 2 bytes address + 6 bytes seqno = 8 bytes + + for (int i = 1; i <= numElements; i++) + { + property(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE)->read(i, 1, entry); + uint16_t addr = (entry[0] << 8) | entry[1]; + + if (addr == indAddr) + { + return i; + } + } + } + + // Not found + return 0; +} + +void SecurityInterfaceObject::setSequenceNumber(bool toolAccess, uint64_t seqNum) +{ + uint8_t seqBytes[6] = {0x00}; + sixBytesFromUInt64(seqNum, seqBytes); + + if (toolAccess) + { + property(PID_TOOL_SEQUENCE_NUMBER_SENDING)->write(1, 1, seqBytes); + } + else + { + property(PID_SEQUENCE_NUMBER_SENDING)->write(1, 1, seqBytes); + } +} + +uint16_t SecurityInterfaceObject::getNumberOfElements(PropertyID propId) +{ + // Get number of entries for this property + uint16_t numElements = 0; + + uint8_t data[sizeof(uint16_t)]; // is sizeof(_currentElements) which is uint16_t + uint8_t count = property(propId)->read(0, 1, data); + + if (count > 0) + { + popWord(numElements, data); + } + + return numElements; +} + +uint64_t SecurityInterfaceObject::getLastValidSequenceNumber(uint16_t deviceAddr) +{ + // Get number of entries for this property + uint16_t numElements = getNumberOfElements(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE); + + if (numElements > 0) + { + uint8_t elementSize = propertySize(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE); + + // Search for individual address + uint8_t entry[elementSize]; // 2 bytes address + 6 bytes seqno = 8 bytes + + for (int i = 1; i <= numElements; i++) + { + property(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE)->read(i, 1, entry); + uint16_t addr = (entry[0] << 8) | entry[1]; + + if (addr == deviceAddr) + { + return sixBytesToUInt64(&entry[2]); + } + } + } + + return 0; +} + +void SecurityInterfaceObject::setLastValidSequenceNumber(uint16_t deviceAddr, uint64_t seqNum) +{ + // Get number of entries for this property + uint16_t numElements = getNumberOfElements(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE); + + if (numElements > 0) + { + uint8_t elementSize = propertySize(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE); + + // Search for individual address + uint8_t entry[elementSize]; // 2 bytes address + 6 bytes seqno = 8 bytes + + for (int i = 1; i <= numElements; i++) + { + property(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE)->read(i, 1, entry); + uint16_t addr = (entry[0] << 8) | entry[1]; + + if (addr == deviceAddr) + { + sixBytesFromUInt64(seqNum, &entry[2]); + property(PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE)->write(i, 1, entry); + } + } + } +} + +DataSecurity SecurityInterfaceObject::getGroupObjectSecurity(uint16_t index) +{ + // security table uses same index as group object table + + uint8_t data[propertySize(PID_GO_SECURITY_FLAGS)]; + + uint8_t count = property(PID_GO_SECURITY_FLAGS)->read(index, 1, data); + + if (count > 0) + { + // write access flags, approved spec. AN158, p.97 + bool conf = (data[0] & 2) == 2; + bool auth = (data[0] & 1) == 1; + return conf ? DataSecurity::AuthConf : auth ? DataSecurity::Auth : DataSecurity::None; + } + + return DataSecurity::None; +} + +#endif + diff --git a/components/knx/src/knx/security_interface_object.h b/components/knx/src/knx/security_interface_object.h new file mode 100644 index 0000000..37f78f9 --- /dev/null +++ b/components/knx/src/knx/security_interface_object.h @@ -0,0 +1,64 @@ +#pragma once + +#include "config.h" +#ifdef USE_DATASECURE + +#include "interface_object.h" +#include "knx_types.h" + +class SecurityInterfaceObject: public InterfaceObject +{ + public: + SecurityInterfaceObject(); + + void masterReset(EraseCode eraseCode, uint8_t channel) override; + + bool isSecurityModeEnabled(); + + bool isLoaded(); + + const uint8_t* toolKey(); // returns single tool key (ETS) + const uint8_t* p2pKey(uint16_t addressIndex); // returns p2p key for IA index + const uint8_t* groupKey(uint16_t addressIndex); // returns group key for group address index + + uint16_t indAddressIndex(uint16_t indAddr); // returns 1-based index of address in security IA table + + void setSequenceNumber(bool toolAccess, uint64_t seqNum); + uint64_t getLastValidSequenceNumber(uint16_t deviceAddr); + void setLastValidSequenceNumber(uint16_t deviceAddr, uint64_t seqNum); + + DataSecurity getGroupObjectSecurity(uint16_t index); + + LoadState loadState(); + uint8_t* save(uint8_t* buffer) override; + const uint8_t* restore(const uint8_t* buffer) override; + uint16_t saveSize() override; + + private: + void setSecurityMode(bool enabled); + + void clearFailureLog(); + void getFailureCounters(uint8_t* data); + uint8_t getFromFailureLogByIndex(uint8_t index, uint8_t* data, uint8_t maxDataLen); + + void errorCode(ErrorCode errorCode); + + void loadEvent(const uint8_t* data); + void loadEventUnloaded(const uint8_t* data); + void loadEventLoading(const uint8_t* data); + void loadEventLoaded(const uint8_t* data); + void loadEventError(const uint8_t* data); + + void loadState(LoadState newState); + LoadState _state = LS_UNLOADED; + + bool _securityModeEnabled {false}; + + uint16_t getNumberOfElements(PropertyID propId); + + // Our FDSK + static const uint8_t _fdsk[]; + static uint8_t _secReport[]; + static uint8_t _secReportCtrl[]; +}; +#endif diff --git a/components/knx/src/knx/service_families.h b/components/knx/src/knx/service_families.h new file mode 100644 index 0000000..e4b49ed --- /dev/null +++ b/components/knx/src/knx/service_families.h @@ -0,0 +1,15 @@ +#ifndef KNX_SERVICE_FAMILY_CORE + #define KNX_SERVICE_FAMILY_CORE 1 +#endif + +#ifndef KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT + #define KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT 1 +#endif + +#ifndef KNX_SERVICE_FAMILY_TUNNELING + #define KNX_SERVICE_FAMILY_TUNNELING 1 +#endif + +#ifndef KNX_SERVICE_FAMILY_ROUTING + #define KNX_SERVICE_FAMILY_ROUTING 1 +#endif \ No newline at end of file diff --git a/components/knx/src/knx/simple_map.h b/components/knx/src/knx/simple_map.h new file mode 100644 index 0000000..c04a79f --- /dev/null +++ b/components/knx/src/knx/simple_map.h @@ -0,0 +1,134 @@ +#pragma once + +// Provides a simple unordered map which is based on two arrays of different data types, namely K and V. +// One array is used for the keys, the other array is used for the values. +// Tracking of free/occupied slots in the arrays is realized by a bitmask of size uint64_t. +// As a result the maximum size of the map is 64 entries. +// If a non-primitive data type is required for the key by using a "class" or "struct", +// then the operator== has to be provided by that class/struct: +// bool operator ==(const K&) const { return true/false; } + +template +class Map +{ + public: + Map() + { + static_assert (SIZE <= 64, "Map is too big! Max. 64 elements."); + } + + void clear() + { + _validEntries = 0; + } + + bool empty() + { + return (_validEntries == 0); + } + + uint8_t size() + { + uint8_t size = 0; + + for (uint8_t i = 0; i < SIZE; i++) + { + size += (((_validEntries >> i) & 0x01) == 0x01) ? 1 : 0; + } + + return size; + } + + bool insert(K key, V value) + { + uint8_t index = getNextFreeIndex(); + + if (index != noFreeEntryFoundIndex) + { + keys[index] = key; + values[index] = value; + + _validEntries |= 1 << index; + return true; + } + + // No free space + return false; + } + + bool insertOrAssign(K key, V value) + { + // Try to find the key + for (uint8_t i = 0; i < SIZE; i++) + { + // Check if this array slot is occupied + if ((_validEntries >> i) & 0x01) + { + // Key found? + if (keys[i] == key) + { + values[i] = value; + return true; + } + } + } + + // Key does not exist, add it if enough space + return insert(key, value); + } + + bool erase(K key) + { + for (uint8_t i = 0; i < SIZE; i++) + { + if ((_validEntries >> i) & 0x01) + { + if (keys[i] == key) + { + _validEntries &= ~(1 << i); + return true; + } + } + } + + return false; + } + + V* get(K key) + { + // Try to find the key + for (uint8_t i = 0; i < SIZE; i++) + { + // Check if this array slot is occupied + if ((_validEntries >> i) & 0x01) + { + // Key found? + if (keys[i] == key) + { + return &values[i]; + } + } + } + + return nullptr; + } + + private: + uint8_t getNextFreeIndex() + { + for (uint8_t i = 0; i < SIZE; i++) + { + if (((_validEntries >> i) & 0x01) == 0) + { + return i; + } + } + + return noFreeEntryFoundIndex; + } + + uint64_t _validEntries{0}; + K keys[SIZE]; + V values[SIZE]; + static constexpr uint8_t noFreeEntryFoundIndex = 255; +}; diff --git a/components/knx/src/knx/table_object.cpp b/components/knx/src/knx/table_object.cpp new file mode 100644 index 0000000..cef83be --- /dev/null +++ b/components/knx/src/knx/table_object.cpp @@ -0,0 +1,414 @@ +#include + +#include "table_object.h" +#include "bits.h" +#include "memory.h" +#include "callback_property.h" +#include "data_property.h" + +BeforeTablesUnloadCallback TableObject::_beforeTablesUnload = 0; +uint8_t TableObject::_tableUnloadCount = 0; + +void TableObject::beforeTablesUnloadCallback(BeforeTablesUnloadCallback func) +{ + _beforeTablesUnload = func; +} + +BeforeTablesUnloadCallback TableObject::beforeTablesUnloadCallback() +{ + return _beforeTablesUnload; +} + +TableObject::TableObject(Memory& memory, uint32_t staticTableAdr, uint32_t staticTableSize) + : _memory(memory) +{ + _staticTableAdr = staticTableAdr; + _staticTableSize = staticTableSize; +} + +TableObject::~TableObject() +{} + +void TableObject::beforeStateChange(LoadState& newState) +{ + if (newState == LS_LOADED && _tableUnloadCount > 0) + _tableUnloadCount--; + + if (_tableUnloadCount > 0) + return; + + if (newState == LS_UNLOADED) + { + _tableUnloadCount++; + + if (_beforeTablesUnload != 0) + _beforeTablesUnload(); + } +} + +LoadState TableObject::loadState() +{ + return _state; +} + +void TableObject::loadState(LoadState newState) +{ + if (newState == _state) + return; + + beforeStateChange(newState); + _state = newState; +} + + +uint8_t* TableObject::save(uint8_t* buffer) +{ + //println("TableObject::save"); + allocTableStatic(); + + buffer = pushByte(_state, buffer); + + buffer = pushInt(_size, buffer); + + if (_data) + buffer = pushInt(_memory.toRelative(_data), buffer); + else + buffer = pushInt(0, buffer); + + return InterfaceObject::save(buffer); +} + + +const uint8_t* TableObject::restore(const uint8_t* buffer) +{ + //println("TableObject::restore"); + + uint8_t state = 0; + buffer = popByte(state, buffer); + _state = (LoadState)state; + + buffer = popInt(_size, buffer); + + uint32_t relativeAddress = 0; + buffer = popInt(relativeAddress, buffer); + //println(relativeAddress); + + if (relativeAddress != 0) + _data = _memory.toAbsolute(relativeAddress); + else + _data = 0; + + //println((uint32_t)_data); + return InterfaceObject::restore(buffer); +} + +uint32_t TableObject::tableReference() +{ + return (uint32_t)_memory.toRelative(_data); +} + +bool TableObject::allocTable(uint32_t size, bool doFill, uint8_t fillByte) +{ + if (_staticTableAdr) + return false; + + if (_data) + { + _memory.freeMemory(_data); + _data = 0; + } + + if (size == 0) + return true; + + _data = _memory.allocMemory(size); + + if (!_data) + return false; + + if (doFill) + { + uint32_t addr = _memory.toRelative(_data); + + for (uint32_t i = 0; i < size; i++) + _memory.writeMemory(addr + i, 1, &fillByte); + } + + _size = size; + + return true; +} + + +void TableObject::allocTableStatic() +{ + if (_staticTableAdr && !_data) + { + _data = _memory.toAbsolute(_staticTableAdr); + _size = _staticTableSize; + _memory.addNewUsedBlock(_data, _size); + } +} + +void TableObject::loadEvent(const uint8_t* data) +{ + //printHex("TableObject::loadEvent 0x", data, 10); + switch (_state) + { + case LS_UNLOADED: + loadEventUnloaded(data); + break; + + case LS_LOADING: + loadEventLoading(data); + break; + + case LS_LOADED: + loadEventLoaded(data); + break; + + case LS_ERROR: + loadEventError(data); + break; + + default: + /* do nothing */ + break; + } +} + +void TableObject::loadEventUnloaded(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_LOAD_COMPLETED: + case LE_ADDITIONAL_LOAD_CONTROLS: + case LE_UNLOAD: + break; + + case LE_START_LOADING: + loadState(LS_LOADING); + break; + + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void TableObject::loadEventLoading(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_START_LOADING: + break; + + case LE_LOAD_COMPLETED: + _memory.saveMemory(); + loadState(LS_LOADED); + break; + + case LE_UNLOAD: + loadState(LS_UNLOADED); + break; + + case LE_ADDITIONAL_LOAD_CONTROLS: + additionalLoadControls(data); + break; + + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void TableObject::loadEventLoaded(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_LOAD_COMPLETED: + break; + + case LE_START_LOADING: + loadState(LS_LOADING); + break; + + case LE_UNLOAD: + loadState(LS_UNLOADED); + + //free nv memory + if (_data) + { + if (!_staticTableAdr) + { + _memory.freeMemory(_data); + _data = 0; + } + } + + break; + + case LE_ADDITIONAL_LOAD_CONTROLS: + loadState(LS_ERROR); + errorCode(E_INVALID_OPCODE); + break; + + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void TableObject::loadEventError(const uint8_t* data) +{ + uint8_t event = data[0]; + + switch (event) + { + case LE_NOOP: + case LE_LOAD_COMPLETED: + case LE_ADDITIONAL_LOAD_CONTROLS: + case LE_START_LOADING: + break; + + case LE_UNLOAD: + loadState(LS_UNLOADED); + break; + + default: + loadState(LS_ERROR); + errorCode(E_GOT_UNDEF_LOAD_CMD); + } +} + +void TableObject::additionalLoadControls(const uint8_t* data) +{ + if (data[1] != 0x0B) // Data Relative Allocation + { + loadState(LS_ERROR); + errorCode(E_INVALID_OPCODE); + return; + } + + size_t size = ((data[2] << 24) | (data[3] << 16) | (data[4] << 8) | data[5]); + bool doFill = data[6] == 0x1; + uint8_t fillByte = data[7]; + + if (!allocTable(size, doFill, fillByte)) + { + loadState(LS_ERROR); + errorCode(E_MAX_TABLE_LENGTH_EXEEDED); + } +} + +uint8_t* TableObject::data() +{ + return _data; +} + +void TableObject::errorCode(ErrorCode errorCode) +{ + uint8_t data = errorCode; + Property* prop = property(PID_ERROR_CODE); + prop->write(data); +} + +uint16_t TableObject::saveSize() +{ + return 5 + InterfaceObject::saveSize() + sizeof(_size); +} + +void TableObject::initializeProperties(size_t propertiesSize, Property** properties) +{ + Property* ownProperties[] = + { + new CallbackProperty(this, PID_LOAD_STATE_CONTROL, true, PDT_CONTROL, 1, ReadLv3 | WriteLv3, + [](TableObject * obj, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + data[0] = obj->_state; + return 1; + }, + [](TableObject * obj, uint16_t start, uint8_t count, const uint8_t* data) -> uint8_t { + obj->loadEvent(data); + return 1; + }) + }; + + uint8_t ownPropertiesCount = sizeof(ownProperties) / sizeof(Property*); + + uint8_t propertyCount = propertiesSize / sizeof(Property*); + uint8_t allPropertiesCount = propertyCount + ownPropertiesCount; + + Property* allProperties[allPropertiesCount]; + memcpy(allProperties, properties, propertiesSize); + memcpy(allProperties + propertyCount, ownProperties, sizeof(ownProperties)); + + if (_staticTableAdr) + InterfaceObject::initializeProperties(sizeof(allProperties), allProperties); + else + initializeDynTableProperties(sizeof(allProperties), allProperties); +} + +void TableObject::initializeDynTableProperties(size_t propertiesSize, Property** properties) +{ + Property* ownProperties[] = + { + new CallbackProperty(this, PID_TABLE_REFERENCE, false, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv0, + [](TableObject * obj, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t { + if (start == 0) + { + uint16_t currentNoOfElements = 1; + pushWord(currentNoOfElements, data); + return 1; + } + + if (obj->_state == LS_UNLOADED) + pushInt(0, data); + else + pushInt(obj->tableReference(), data); + return 1; + }), + new CallbackProperty(this, PID_MCB_TABLE, false, PDT_GENERIC_08, 1, ReadLv3 | WriteLv0, + [](TableObject * obj, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t { + if (obj->_state != LS_LOADED) + return 0; // need to check return code for invalid + + uint32_t segmentSize = obj->_size; + uint16_t crc16 = crc16Ccitt(obj->data(), segmentSize); + + pushInt(segmentSize, data); // Segment size + pushByte(0x00, data + 4); // CRC control byte -> 0: always valid + pushByte(0xFF, data + 5); // Read access 4 bits + Write access 4 bits + pushWord(crc16, data + 6); // CRC-16 CCITT of data + + return 1; + }), + new DataProperty(PID_ERROR_CODE, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0, (uint8_t)E_NO_FAULT) + }; + + uint8_t ownPropertiesCount = sizeof(ownProperties) / sizeof(Property*); + + uint8_t propertyCount = propertiesSize / sizeof(Property*); + uint8_t allPropertiesCount = propertyCount + ownPropertiesCount; + + Property* allProperties[allPropertiesCount]; + memcpy(allProperties, properties, propertiesSize); + memcpy(allProperties + propertyCount, ownProperties, sizeof(ownProperties)); + + InterfaceObject::initializeProperties(sizeof(allProperties), allProperties); +} \ No newline at end of file diff --git a/components/knx/src/knx/table_object.h b/components/knx/src/knx/table_object.h new file mode 100644 index 0000000..914bb2e --- /dev/null +++ b/components/knx/src/knx/table_object.h @@ -0,0 +1,94 @@ +#pragma once + +#include "interface_object.h" + +class Memory; + +typedef void (*BeforeTablesUnloadCallback)(); + +/** + * This class provides common functionality for interface objects that are configured by ETS with MemorWrite. + */ +class TableObject: public InterfaceObject +{ + friend class Memory; + + public: + /** + * The constuctor. + * @param memory The instance of the memory management class to use. + */ + TableObject(Memory& memory, uint32_t staticTableAdr = 0, uint32_t staticTableSize = 0); + + /** + * The destructor. + */ + virtual ~TableObject(); + /** + * This method returns the ::LoadState of the interface object. + */ + LoadState loadState(); + uint8_t* save(uint8_t* buffer) override; + const uint8_t* restore(const uint8_t* buffer) override; + uint16_t saveSize() override; + + static void beforeTablesUnloadCallback(BeforeTablesUnloadCallback func); + static BeforeTablesUnloadCallback beforeTablesUnloadCallback(); + + protected: + /** + * This method is called before the interface object enters a new ::LoadState. + * If there is a error changing the state newState should be set to ::LS_ERROR and errorCode() + * to a reason for the failure. + */ + virtual void beforeStateChange(LoadState& newState); + + /** + * returns the internal data of the interface object. This pointer belongs to the TableObject class and + * must not be written at nor freed. + */ + uint8_t* data(); + /** + * Set the reason for a state change failure. + */ + void errorCode(ErrorCode errorCode); + + void initializeProperties(size_t propertiesSize, Property** properties) override; + + static BeforeTablesUnloadCallback _beforeTablesUnload; + + Memory& _memory; + + private: + uint32_t tableReference(); + bool allocTable(uint32_t size, bool doFill, uint8_t fillByte); + void allocTableStatic(); + void initializeDynTableProperties(size_t propertiesSize, Property** properties); + void loadEvent(const uint8_t* data); + void loadEventUnloaded(const uint8_t* data); + void loadEventLoading(const uint8_t* data); + void loadEventLoaded(const uint8_t* data); + void loadEventError(const uint8_t* data); + void additionalLoadControls(const uint8_t* data); + + /** + * set the ::LoadState of the interface object. + * + * Calls beforeStateChange(). + * + * @param newState the new ::LoadState + */ + void loadState(LoadState newState); + LoadState _state = LS_UNLOADED; + uint8_t* _data = 0; + static uint8_t _tableUnloadCount; + uint32_t _staticTableAdr; + uint32_t _staticTableSize; + + /** + * used to store size of data() in allocTable(), needed for calculation of crc in PID_MCB_TABLE. + * This value is also saved and restored. + * The size of the memory block cannot be used because it is changed during alignment to page size. + */ + uint32_t _size = 0; +}; diff --git a/components/knx/src/knx/tp_frame.h b/components/knx/src/knx/tp_frame.h new file mode 100644 index 0000000..7f576fe --- /dev/null +++ b/components/knx/src/knx/tp_frame.h @@ -0,0 +1,309 @@ +#pragma once +#pragma GCC optimize("O3") + +#include "cemi_frame.h" +#include +#include +#include + +// Means that the frame is invalid +#define TP_FRAME_FLAG_INVALID 0b10000000 + +// Means that the frame is an extended frame +#define TP_FRAME_FLAG_EXTENDED 0b01000000 + +// Means that the frame has been repeated +#define TP_FRAME_FLAG_REPEATED 0b00100000 + +// Means that the frame comes from the device itself +#define TP_FRAME_FLAG_ECHO 0b00010000 + +// Means that the frame is processed by this device +#define TP_FRAME_FLAG_ADDRESSED 0b00001000 + +// Means that the frame has been acked with BUSY +#define TP_FRAME_FLAG_ACK_BUSY 0b00000100 + +// Means that the frame has been acked with NACK +#define TP_FRAME_FLAG_ACK_NACK 0b00000010 + +// Means that the frame has been acked +#define TP_FRAME_FLAG_ACK 0b00000001 + +class TpFrame +{ + private: + uint8_t* _data; + uint16_t _size; + uint16_t _maxSize; + uint8_t _flags = 0; + + /* + * Sets a few flags based on the control byte + */ + inline void presetFlags() + { + if (isExtended()) + addFlags(TP_FRAME_FLAG_EXTENDED); + + if (isRepeated()) + addFlags(TP_FRAME_FLAG_REPEATED); + } + + public: + /* + * Convert a CemiFrame into a TpFrame + */ + TpFrame(CemiFrame& cemiFrame) + { + _size = cemiFrame.telegramLengthtTP(); + _maxSize = cemiFrame.telegramLengthtTP(); + _data = (uint8_t*)malloc(cemiFrame.telegramLengthtTP()); + cemiFrame.fillTelegramTP(_data); + presetFlags(); + } + + /* + * Create a TpFrame with a reserved space. + * Used for incoming parsing. + */ + TpFrame(uint16_t maxSize = 263) + : _maxSize(maxSize) + { + _data = (uint8_t*)malloc(_maxSize); + _size = 0; + } + + /* + * Free the data area + */ + ~TpFrame() + { + free(_data); + } + + /* + * Add a byte at end. + * Used for incoming parsing. + */ + inline void addByte(uint8_t byte) + { + if (!isFull()) + { + _data[_size] = byte; + _size++; + } + + // Read meta data for flags + if (_size == 1) + presetFlags(); + } + + /* + * Current frame size. This may differ from the actual size as long as the frame is not complete. + */ + inline uint16_t size() + { + return _size; + } + + /* + * Returns the assigned flags + */ + inline uint16_t flags() + { + return _flags; + } + + /* + * Adds one or more flags + */ + inline void addFlags(uint8_t flags) + { + _flags |= flags; + } + + /* + * Returns a pointer to the data + */ + inline uint8_t* data() + { + return _data; + } + + /* + * Returns the byte corresponding to the specified position + */ + inline uint8_t data(uint16_t pos) + { + return _data[pos]; + } + + /* + * Resets the internal values to refill the frame. + */ + inline void reset() + { + _size = 0; + _flags = 0; + // It is important to fill the _data with zeros so that the length is 0 as long as the value has not yet been read in. + memset(_data, 0x0, _maxSize); + } + + /* + * Checks whether the frame has been imported completely + */ + inline bool isFull() + { + return _size >= (_size >= 7 ? fullSize() : _maxSize); + } + + /* + * Returns is the frame exteneded or not + */ + inline bool isExtended() + { + return (_data[0] & 0xD3) == 0x10; + } + + /* + * Returns the source + * Assumes that enough data has been imported. + */ + inline uint16_t source() + { + return isExtended() ? (_data[2] << 8) + _data[3] : (_data[1] << 8) + _data[2]; + } + + inline std::string humanSource() + { + uint16_t value = source(); + char buffer[10]; + sprintf(buffer, "%02i.%02i.%03i", (value >> 12 & 0b1111), (value >> 8 & 0b1111), (value & 0b11111111)); + return buffer; + } + + inline std::string humanDestination() + { + uint16_t value = destination(); + char buffer[10]; + + if (isGroupAddress()) + sprintf(buffer, "%02i/%02i/%03i", (value >> 11 & 0b1111), (value >> 8 & 0b111), (value & 0b11111111)); + else + sprintf(buffer, "%02i.%02i.%03i", (value >> 12 & 0b1111), (value >> 8 & 0b1111), (value & 0b11111111)); + + return buffer; + } + + /* + * Returns the destination + * Assumes that enough data has been imported. + */ + inline uint16_t destination() + { + return isExtended() ? (_data[4] << 8) + _data[5] : (_data[3] << 8) + _data[4]; + } + + /* + * Returns the payload size (with checksum) + * Assumes that enough data has been imported. + */ + inline uint8_t payloadSize() + { + return isExtended() ? _data[6] : _data[5] & 0b1111; + } + + /* + * Returns the header size + */ + inline uint8_t headerSize() + { + return isExtended() ? 9 : 8; + } + + /* + * Returns the frame size based on header and payload size. + * Assumes that enough data has been imported. + */ + inline uint16_t fullSize() + { + return headerSize() + payloadSize(); + } + + /* + * Returns if the destination is a group address + * Assumes that enough data has been imported. + */ + inline bool isGroupAddress() + { + return isExtended() ? (_data[1] >> 7) & 0b1 : (_data[5] >> 7) & 0b1; + } + + /* + * Calculates the size of a CemiFrame. A CemiFrame has 2 additional bytes at the beginning. + * An additional byte is added to a standard frame, as this still has to be converted into an extendend. + */ + uint16_t cemiSize() + { + return fullSize() + (isExtended() ? 2 : 3) - 1; // -1 without CRC + } + + /** + * Creates a buffer and converts the TpFrame into a CemiFrame. + * Important: After processing (i.e. also after using the CemiFrame), the reference must be released manually. + */ + uint8_t* cemiData() + { + uint8_t* cemiBuffer = (uint8_t*)malloc(cemiSize()); + + // Das CEMI erwartet die Daten im Extended format inkl. zwei zusätzlicher Bytes am Anfang. + cemiBuffer[0] = 0x29; + cemiBuffer[1] = 0x0; + cemiBuffer[2] = _data[0]; + + if (isExtended()) + { + memcpy(cemiBuffer + 2, _data, fullSize() - 1); // -1 without CRC + } + else + { + cemiBuffer[3] = _data[5] & 0xF0; + memcpy(cemiBuffer + 4, _data + 1, 4); + cemiBuffer[8] = _data[5] & 0x0F; + memcpy(cemiBuffer + 9, _data + 6, cemiBuffer[8] + 2 - 1); // -1 without CRC + } + + return cemiBuffer; + } + + /* + * Checks whether the frame is complete and valid. + */ + inline bool isValid() + { + if (!isComplete()) + return false; + + uint8_t sum = 0; + const uint16_t s = fullSize() - 1; + + for (uint16_t i = 0; i < s; i++) + sum ^= _data[i]; + + return _data[s] == (uint8_t)~sum; + } + + /* + * Checks whether the frame is long enough to match the length specified in the frame + */ + inline bool isComplete() + { + return _size == fullSize(); + } + + inline bool isRepeated() + { + return !(_data[0] & 0b100000); + } +}; \ No newline at end of file diff --git a/components/knx/src/knx/tpdu.cpp b/components/knx/src/knx/tpdu.cpp new file mode 100644 index 0000000..c5b2547 --- /dev/null +++ b/components/knx/src/knx/tpdu.cpp @@ -0,0 +1,132 @@ +#include "tpdu.h" +#include "cemi_frame.h" + +TPDU::TPDU(uint8_t* data, CemiFrame& frame): _data(data), _frame(frame) +{ +} + +TpduType TPDU::type() const +{ + if (control()) + { + if (numbered()) + { + if ((_data[0] & 1) == 0) + return Ack; + else + return Nack; + } + else if ((_data[0] & 1) == 0) + return Connect; + else + return Disconnect; + } + else + { + if (_frame.addressType() == GroupAddress) + { + if (_frame.destinationAddress() == 0) + return DataBroadcast; + else + return DataGroup; + } + else if (numbered()) + return DataConnected; + else + return DataInduvidual; + } +} + +void TPDU::type(TpduType type) +{ + switch (type) + { + case DataBroadcast: + case DataGroup: + case DataInduvidual: + _data[0] &= 0x3; + break; + + case DataConnected: + _data[0] &= 0xC3; + _data[0] |= 0x40; + break; + + case Connect: + _data[0] = 0x80; + break; + + case Disconnect: + _data[0] = 0x81; + break; + + case Ack: + _data[0] &= ~0xC3; + _data[0] |= 0xC2; + break; + + case Nack: + _data[0] |= 0xC3; + break; + } +} + +bool TPDU::numbered() const +{ + return (_data[0] & 0x40) > 0; +} + +void TPDU::numbered(bool value) +{ + if (value) + _data[0] |= 0x40; + else + _data[0] &= ~0x40; +} + +bool TPDU::control() const +{ + return (_data[0] & 0x80) > 0; +} + +void TPDU::control(bool value) +{ + if (value) + _data[0] |= 0x80; + else + _data[0] &= ~0x80; +} + +uint8_t TPDU::sequenceNumber() const +{ + return ((_data[0] >> 2) & 0xF); +} + +void TPDU::sequenceNumber(uint8_t value) +{ + _data[0] &= ~(0xF << 2); + _data[0] |= ((value & 0xF) << 2); +} + +APDU& TPDU::apdu() +{ + return _frame.apdu(); +} + +CemiFrame& TPDU::frame() +{ + return _frame; +} + +void TPDU::printPDU() +{ + /* print.print("TPDU: "); + print.print(type(), HEX, 2); + print.print(" "); + for (uint8_t i = 0; i < apdu().length() + 1; ++i) + { + if (i) print.print(" "); + print.print(_data[i], HEX, 2); + } + print.println()*/; +} \ No newline at end of file diff --git a/components/knx/src/knx/tpdu.h b/components/knx/src/knx/tpdu.h new file mode 100644 index 0000000..3dad00a --- /dev/null +++ b/components/knx/src/knx/tpdu.h @@ -0,0 +1,36 @@ +#pragma once + +#include "stdint.h" +#include "knx_types.h" +class CemiFrame; +class APDU; + +class TPDU +{ + friend class CemiFrame; + + public: + TpduType type() const; + void type(TpduType type); + + bool numbered() const; + void numbered(bool value); + + bool control() const; + void control(bool value); + + uint8_t sequenceNumber() const; + void sequenceNumber(uint8_t value); + + APDU& apdu(); + + CemiFrame& frame(); + void printPDU(); + + protected: + TPDU(uint8_t* data, CemiFrame& frame); + + private: + uint8_t* _data = 0; + CemiFrame& _frame; +}; diff --git a/components/knx/src/knx/tpuart_data_link_layer.cpp b/components/knx/src/knx/tpuart_data_link_layer.cpp new file mode 100644 index 0000000..12a8673 --- /dev/null +++ b/components/knx/src/knx/tpuart_data_link_layer.cpp @@ -0,0 +1,1250 @@ +#include "config.h" +#ifdef USE_TP +#pragma GCC optimize("O3") + +#include "address_table_object.h" +#include "bits.h" +#include "cemi_frame.h" +#include "device_object.h" +#include "platform.h" +#include "tpuart_data_link_layer.h" + +/* + * A new implementation of the tpuart connection. + * Author Marco Scholl + * + */ + +// services Host -> Controller : +// internal commands, device specific +#define U_RESET_REQ 0x01 +#define U_STATE_REQ 0x02 +#define U_SET_BUSY_REQ 0x03 +#define U_QUIT_BUSY_REQ 0x04 +#define U_BUSMON_REQ 0x05 +#define U_SET_ADDRESS_REQ 0xF1 // different on TP-UART +#define U_L_DATA_OFFSET_REQ 0x08 //-0x0C +#define U_SYSTEM_MODE 0x0D +#define U_STOP_MODE_REQ 0x0E +#define U_EXIT_STOP_MODE_REQ 0x0F +#define U_ACK_REQ 0x10 //-0x17 +#define U_ACK_REQ_NACK 0x04 +#define U_ACK_REQ_BUSY 0x02 +#define U_ACK_REQ_ADRESSED 0x01 +#define U_POLLING_STATE_REQ 0xE0 + +// Only on NCN51xx available +#ifdef NCN5120 + #define U_CONFIGURE_REQ 0x18 + #define U_CONFIGURE_MARKER_REQ 0x1 + #define U_CONFIGURE_CRC_CCITT_REQ 0x2 + #define U_CONFIGURE_AUTO_POLLING_REQ 0x4 + #define U_SET_REPETITION_REQ 0xF2 +#else + #define U_MXRSTCNT 0x24 +#endif + +// knx transmit data commands +#define U_L_DATA_START_REQ 0x80 +#define U_L_DATA_CONT_REQ 0x80 //-0xBF +#define U_L_DATA_END_REQ 0x40 //-0x7F + +// serices to host controller + +// DLL services (device is transparent) +#define L_DATA_STANDARD_IND 0x90 +#define L_DATA_EXTENDED_IND 0x10 +#define L_DATA_MASK 0xD3 +#define L_POLL_DATA_IND 0xF0 + +// acknowledge services (device is transparent in bus monitor mode) +#define L_ACKN_IND 0x00 +#define L_ACKN_MASK 0x33 +#define L_ACKN_BUSY_MASK 0x0C +#define L_ACKN_NACK_MASK 0xC0 +#define L_DATA_CON 0x0B +#define L_DATA_CON_MASK 0x7F +#define SUCCESS 0x80 + +// control services, device specific +#define U_RESET_IND 0x03 +#define U_STATE_MASK 0x07 +#define U_STATE_IND 0x07 +#define SLAVE_COLLISION 0x80 +#define RECEIVE_ERROR 0x40 +#define TRANSMIT_ERROR 0x20 +#define PROTOCOL_ERROR 0x10 +#define TEMPERATURE_WARNING 0x08 +#define U_FRAME_STATE_IND 0x13 +#define U_FRAME_STATE_MASK 0x17 +#define PARITY_BIT_ERROR 0x80 +#define CHECKSUM_LENGTH_ERROR 0x40 +#define TIMING_ERROR 0x20 +#define U_CONFIGURE_IND 0x01 +#define U_CONFIGURE_MASK 0x83 +#define AUTO_ACKNOWLEDGE 0x20 +#define AUTO_POLLING 0x10 +#define CRC_CCITT 0x80 +#define FRAME_END_WITH_MARKER 0x40 +#define U_FRAME_END_IND 0xCB +#define U_STOP_MODE_IND 0x2B +#define U_SYSTEM_STAT_IND 0x4B + +/* + * NCN51xx Register handling + */ +// write internal registers +#define U_INT_REG_WR_REQ_WD 0x28 +#define U_INT_REG_WR_REQ_ACR0 0x29 +#define U_INT_REG_WR_REQ_ACR1 0x2A +#define U_INT_REG_WR_REQ_ASR0 0x2B +// read internal registers +#define U_INT_REG_RD_REQ_WD 0x38 +#define U_INT_REG_RD_REQ_ACR0 0x39 +#define U_INT_REG_RD_REQ_ACR1 0x3A +#define U_INT_REG_RD_REQ_ASR0 0x3B +// Analog Control Register 0 - Bit values +#define ACR0_FLAG_V20VEN 0x40 +#define ACR0_FLAG_DC2EN 0x20 +#define ACR0_FLAG_XCLKEN 0x10 +#define ACR0_FLAG_TRIGEN 0x08 +#define ACR0_FLAG_V20VCLIMIT 0x04 + +enum +{ + TX_IDLE, + TX_FRAME +}; + +enum +{ + // In this state, the system waits for new control commands. + RX_IDLE, + + // In this state, all bytes are regarded as bytes for a frame. + RX_FRAME, + + // In this state, all bytes are discarded + RX_INVALID, + + // Monitoring is still waiting for an ACk + RX_AWAITING_ACK +}; + +void printFrame(TpFrame* tpframe) +{ + print(tpframe->humanSource().c_str()); + print(" -> "); + print(tpframe->humanDestination().c_str()); + print(" ["); + print((tpframe->flags() & TP_FRAME_FLAG_INVALID) ? 'I' : '_'); // Invalid + print((tpframe->flags() & TP_FRAME_FLAG_EXTENDED) ? 'E' : '_'); // Extended + print((tpframe->flags() & TP_FRAME_FLAG_REPEATED) ? 'R' : '_'); // Repeat + print((tpframe->flags() & TP_FRAME_FLAG_ECHO) ? 'T' : '_'); // Send by me + print((tpframe->flags() & TP_FRAME_FLAG_ADDRESSED) ? 'D' : '_'); // Recv for me + print((tpframe->flags() & TP_FRAME_FLAG_ACK_NACK) ? 'N' : '_'); // ACK + NACK + print((tpframe->flags() & TP_FRAME_FLAG_ACK_BUSY) ? 'B' : '_'); // ACK + BUSY + print((tpframe->flags() & TP_FRAME_FLAG_ACK) ? 'A' : '_'); // ACK + print("] "); + printHex("( ", tpframe->data(), tpframe->size(), false); + print(")"); +} + +/* + * Processes all bytes. + */ +void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) +{ + if (!isrLock()) + return; + + /* + * Some platforms support the detection of whether the hardware buffer has overflowed. + * Theoretically, you could now discard the buffer, but then a valid frame may be lost. + * Therefore, only one piece of information is output later in the loop and byte processing "tries" to respond to it. + */ + if (_platform.overflowUart()) + _rxOverflow = true; + + // process data + while (_platform.uartAvailable()) + { + processRxByte(); + } + + isrUnlock(); +} + +/* + * Processes 1 incoming byte (if available) + */ +void TpUartDataLinkLayer::processRxByte() +{ + int byte = _platform.readUart(); + + // RxBuffer empty + if (byte < 0) + return; + + /* + * If I am in RX_INVALID mode + * and the last byte was processed more than 2ms ago (i.e. pause >2ms) + * and there are no more bytes in the buffer, + * then I can discard the INVALID state. + */ + if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) + { + processRxFrameComplete(); + _rxState = RX_IDLE; + } + + if (_rxState == RX_INVALID) + { + /* + * As soon as a frame has been processed invalidly or an unknown command arrives, the status changes to RX_INVALID. + * From now on I must assume that there has been a transmission error and the current bytes are invalid. + * The same applies if a HW overflow is detected. + * + * The time of the last frame is 3ms past and there is no more data in the buffer. (Is checked by me) + * - If the marker mode is active and a U_FRAME_END_IND has been detected correctly. (Checked here) + * + * Otherwise this section does nothing and thus discards the invalid bytes + */ + if (markerMode()) + { + if (!_rxMarker && byte == U_FRAME_END_IND) + { + _rxMarker = true; + } + else if (_rxMarker && byte == U_FRAME_END_IND) + { + // double byte found so reset marker - no frame end + _rxMarker = false; + } + else if (_rxMarker) + { + // frame end found. -> RX_IDLE + _rxMarker = false; + _rxState = RX_IDLE; + } + } + } + else if (_rxState == RX_FRAME) + { + processRxFrameByte(byte); + } + else if ((byte & L_DATA_MASK) == L_DATA_STANDARD_IND || (byte & L_DATA_MASK) == L_DATA_EXTENDED_IND) + { + /* + * Process a previous frame if still available. This should normally only occur in the bus monitor because an ACK is also being waited for here + */ + processRxFrameComplete(); + _rxFrame->addByte(byte); + + // Provoke invalid frames for tests + // if (millis() % 20 == 0) + // _rxFrame->addByte(0x1); + + _rxMarker = false; + _rxState = RX_FRAME; + + /* + * Here an ack is set inital without Addressed. This is used if an Ack is still set from the previous frame, + * is set back. This happens if processing is delayed too much (e.g. because no DMA/IRQ is used). + * The ACK can be sent as often as required because it is only stored in the BCU and is only used / sent when required. + * + * Of course, you can only do this if you are not sending yourself, as you do not ACK your own frames. The BCU may ignore this, + * but I wanted to be on the safe side here. + */ + if (_txState == TX_IDLE) + { + _platform.writeUart(U_ACK_REQ); + } + } + else + { + // The commands are evaluated here, if this has already happened. + + if (byte == U_RESET_IND) + { + // println("U_RESET_IND"); + } + else if ((byte & U_STATE_MASK) == U_STATE_IND) + { + _tpState |= (byte ^ U_STATE_MASK); +#ifndef NCN5120 + /* + * Filter "Protocol errors" because this is set on other BCUs such as the Siements when the timing is not correct. + * Unfortunately, perfect timing is not possible, so this error must be ignored. Also has no known effects. + */ + _tpState &= 0b11101000; +#endif + } + else if ((byte & U_CONFIGURE_MASK) == U_CONFIGURE_IND) + { + // println("U_CONFIGURE_IND"); + } + else if (byte == U_STOP_MODE_IND) + { + // println("U_STOP_MODE_IND"); + } + else if ((byte & L_ACKN_MASK) == L_ACKN_IND) + { + /* + * If a frame has not yet been closed and an Ack comes in. + * then set the ACK. + */ + if (_rxFrame->size() > 0) + { + if (!(byte & L_ACKN_BUSY_MASK)) + _rxFrame->addFlags(TP_FRAME_FLAG_ACK_BUSY); + + if (!(byte & L_ACKN_NACK_MASK)) + _rxFrame->addFlags(TP_FRAME_FLAG_ACK_NACK); + + _rxFrame->addFlags(TP_FRAME_FLAG_ACK); + processRxFrameComplete(); + } + + // println("L_ACKN_IND"); + } + else if ((byte & L_DATA_CON_MASK) == L_DATA_CON) + { + if (_txState == TX_FRAME) + { + const bool success = ((byte ^ L_DATA_CON_MASK) >> 7); + processTxFrameComplete(success); + } + else + { + // This byte was not expected because nothing was sent. + _rxUnkownControlCounter++; + _rxState = RX_INVALID; + // println("L_DATA_CON"); + } + } + else if (byte == L_POLL_DATA_IND) + { + // println("L_POLL_DATA_IND"); + } + else if ((byte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) + { + // println("U_FRAME_STATE_IND"); + } + else + { + _rxUnkownControlCounter++; + // print("Unknown Controlbyte: "); + // println(byte, HEX); + _rxState = RX_INVALID; + } + } + + _rxLastTime = millis(); +} + +/* + * Process incoming byte of a frame + */ +void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) +{ + /* + * If the maker is active, the first U_FRAME_END_IND must be ignored and a subsequent byte must be waited for. + * The subsequent byte is therefore decisive for how this byte is to be evaluated. + */ + if (markerMode() && (byte == U_FRAME_END_IND && !_rxMarker)) + { + _rxMarker = true; + } + + /* + * If the previous byte was a U_FRAME_END_IND and the new byte is a U_FRAME_STATE_IND, + * then the reception is cleanly completed and the frame can be processed. + */ + else if (_rxMarker && (byte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) + { + _rxMarker = false; + processRxFrameComplete(); + + /* + * Set the status to RX_IDLE, as the marker ensures, + * that the frame has been processed successfully. Subsequent bytes are therefore clean again Control commands, + * even if the frame was discarded due to an invalid checksum (which would mean RX_INVAID) + */ + _rxState = RX_IDLE; + } + + /* + * This is a hypothetical case in which the frames are sent without markers even though marker mode is active. + * Here the current frame is processed and RX_INVALID is set, as the current byte is not processed. + * This case can occur if the marker mode is not supported by the TPUart (NCN51xx feature) but has been activated. + */ + else if (markerMode() && _rxFrame->isFull()) + { + processRxFrameComplete(); + /* + * RX_INVALID because theoretically the frame could have been processed as valid. + * However, since the current byte has already been "started" to be processed, it is missing in the processing chain + * and therefore the subsequent bytes cannot be used. + */ + _rxState = RX_INVALID; + } + + /* + * If marker mode is active, the byte should be processed normally. + * If marker mode is active, a U_FRAME_END_IND byte may only be processed if the previous byte was also a U_FRAME_END_IND. + */ + else if (!markerMode() || byte != U_FRAME_END_IND || (byte == U_FRAME_END_IND && _rxMarker)) + { + // Reset the marker if active + _rxMarker = false; + // Accept the byte + _rxFrame->addByte(byte); + + // If the bus monitor has been started, no processing takes place - i.e. no ACKing + if (!_monitoring) + { + // If more than 7 bytes are available, you can check whether the frame is intended for "me". + if (_rxFrame->size() == 7) + { + // Check whether I am responsible for the frame + TPAckType ack = _cb.isAckRequired(_rxFrame->destination(), _rxFrame->isGroupAddress()); + + if (_forceAck || ack) + { + /* + * Save the responsibility that this frame is to be processed further. + * Since there is no extra function apart from the isAckRequired, this is initially treated the same. + * A later differentiation (possibly for router mode) must then be looked at. + */ + + _rxFrame->addFlags(TP_FRAME_FLAG_ADDRESSED); + + // Of course, this is only allowed if I am not sending myself, as you cannot ACK your own frames + if (_txState == TX_IDLE) + { + // Save that Acking should take place + _rxFrame->addFlags(TP_FRAME_FLAG_ACK); + + // and in the TPUart so that it can send the ACK + _platform.writeUart(U_ACK_REQ | ack); + } + } + } + +#ifdef USE_TP_RX_QUEUE + + // Now check whether the RxQueue still has space for Frame + Size (2) + Flags(1) + if (_rxFrame->size() == 8 && (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED)) + { + if (availableInRxQueue() < (_rxFrame->size() + 3)) + { + // Only if I am not sending myself + if (_txState == TX_IDLE) + { + _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED | U_ACK_REQ_BUSY); + } + } + } + +#endif + } + } + + /* + * If no marker mode is active, the frame must be checked to see if it is complete. + * isFull checks here whether the maxSize or the length specification of the frame has been exceeded! + * In both cases, the frame must be processed. + */ + if (!markerMode() && (_rxFrame->isFull())) + { + processRxFrameComplete(); + } +} + +/* + * Processes the current frame and checks whether it is complete and valid (checksum). + * If a frame is complete and valid, it is placed in the queue if it is intended for "me" and the mode is RX_IDLE again. + * Otherwise the frame is discarded as invalid and the status is RX_INVALID, as it is not guaranteed that subsequent bytes are control codes again. + * Exception in marker mode, here the status RX_INVALID is changed directly back to RX_IDLE at another point because + * it is then ensured that the frame has been broken at TP level. + */ +void TpUartDataLinkLayer::processRxFrameComplete() +{ + // If no frame is currently being edited, then cancel + if (!_rxFrame->size()) + return; + + // Is the frame complete and valid + if (_rxFrame->isValid()) + { + // When a frame has been sent + if (_txState == TX_FRAME) + { + // check whether the receive corresponds to this: comparison of the source address and destination address and start byte without taking the retry bit into account + if (!((_rxFrame->data(0) ^ _txFrame->data(0)) & ~0x20) && _rxFrame->destination() == _txFrame->destination() && _rxFrame->source() == _txFrame->source()) + { + // and mark this accordingly + // println("MATCH"); + _rxFrame->addFlags(TP_FRAME_FLAG_ECHO); + } + + // Now wait for the L_DATA_CON + } + + // if the frame is for me or i am in busmonitor mode then i want to process it further + if (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED || _monitoring) + { + /* + * In bus monitor mode, you still have to wait for an Ack. + * Therefore, the status is changed here and jumps back before the real completion. + * As soon as another call is made (regardless of whether or not the frame has been acked), the frame is closed. + */ + if (_monitoring && _rxState != RX_AWAITING_ACK) + { + _rxState = RX_AWAITING_ACK; + return; + } + + _rxProcessdFrameCounter++; + } + else + { + // Otherwise, discard the package and release the memory -> as it is not packed into the queue + _rxIgnoredFrameCounter++; + } + + // And ready for control codes again + _rxState = RX_IDLE; + } + else + { + /* + * If the frame is incomplete or invalid then switch to RX_INVALID mode as I cannot distinguish, + * whether it is a TPBus error or a UART error or a Timming error. + */ + _rxInvalidFrameCounter++; + _rxFrame->addFlags(TP_FRAME_FLAG_INVALID); + _rxState = RX_INVALID; // ignore bytes + } + +#ifdef USE_TP_RX_QUEUE + pushRxFrameQueue(); +#else + processRxFrame(_rxFrame); +#endif + + // resets the current frame pointer + _rxFrame->reset(); +} + +void TpUartDataLinkLayer::clearTxFrame() +{ + if (_txFrame != nullptr) + { + delete _txFrame; + _txFrame = nullptr; + } +} + +void TpUartDataLinkLayer::clearTxFrameQueue() +{ +} + +void TpUartDataLinkLayer::processTxFrameComplete(bool success) +{ + uint8_t* cemiData = _txFrame->cemiData(); + CemiFrame cemiFrame(cemiData, _txFrame->cemiSize()); + dataConReceived(cemiFrame, success); + free(cemiData); + clearTxFrame(); + _txProcessdFrameCounter++; + _txState = TX_IDLE; +} + +/* + * Puts the frame to be sent into a queue, as the TpUart may not yet be ready to send. + */ +void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame* tpFrame) +{ + knx_tx_queue_entry_t* entry = new knx_tx_queue_entry_t(tpFrame); + + if (_txFrameQueue.back == nullptr) + { + _txFrameQueue.front = _txFrameQueue.back = entry; + } + else + { + _txFrameQueue.back->next = entry; + _txFrameQueue.back = entry; + } + + _txQueueCount++; +} + +void TpUartDataLinkLayer::setRepetitions(uint8_t nack, uint8_t busy) +{ + _repetitions = (nack & 0b111) | ((busy & 0b111) << 4); +} + +// Alias +void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) +{ + setRepetitions(nack, busy); +} + +bool TpUartDataLinkLayer::sendFrame(CemiFrame& cemiFrame) +{ + _txFrameCounter++; + + if (!_connected || _monitoring || _txQueueCount > MAX_TX_QUEUE) + { + if (_txQueueCount > MAX_TX_QUEUE) + { + println("Ignore frame because transmit queue is full!"); + } + + dataConReceived(cemiFrame, false); + return false; + } + + TpFrame* tpFrame = new TpFrame(cemiFrame); + // printHex(" TP>: ", tpFrame->data(), tpFrame->size()); + pushTxFrameQueue(tpFrame); + return true; +} + +/* + * The status should be queried regularly to detect a disconnect of the TPUart and its status. + * In addition, the current config or mode should be transmitted regularly so that after a disconnect, + * the TPUart is in the correct state. + */ +void TpUartDataLinkLayer::requestState(bool force /* = false */) +{ + if (!force) + { + if (!(_rxState == RX_IDLE || _rxState == RX_INVALID)) + return; + + // Only 1x per second + if ((millis() - _lastStateRequest) < 1000) + return; + } + + // println("requestState"); + + // Send configuration or mode + if (_monitoring) + _platform.writeUart(U_BUSMON_REQ); + else + requestConfig(); + + // Question status on - if monitoring inactive + if (!_monitoring) + _platform.writeUart(U_STATE_REQ); + + _lastStateRequest = millis(); +} + +/* + * Sends the current config to the chip + */ +void TpUartDataLinkLayer::requestConfig() +{ + // println("requestConfig"); +#ifdef NCN5120 + if (markerMode()) + _platform.writeUart(U_CONFIGURE_REQ | U_CONFIGURE_MARKER_REQ); + +#endif + + // Set Address for AutoACK Unicast + const uint16_t address = _deviceObject.individualAddress(); + _platform.writeUart(U_SET_ADDRESS_REQ); + _platform.writeUart((address >> 8) & 0xFF); + _platform.writeUart(address & 0xFF); +#ifdef NCN5120 + _platform.writeUart(0xFF); // Dummy Byte needed by NCN only +#endif + + // Deviating Config + if (_repetitions != 0b00110011) + { +#ifdef NCN5120 + _platform.writeUart(U_SET_REPETITION_REQ); + _platform.writeUart(_repetitions); + _platform.writeUart(0x0); // dummy, see NCN5120 datasheet + _platform.writeUart(0x0); // dummy, see NCN5120 datasheet +#else + _platform.writeUart(U_MXRSTCNT); + _platform.writeUart(((_repetitions & 0xF0) << 1) | (_repetitions & 0x0F)); +#endif + } +} + +/* + * A simplified lock mechanism that only works on the same core. + * Perfect for ISR + */ +bool TpUartDataLinkLayer::isrLock(bool blocking /* = false */) +{ + if (blocking) + while (_rxProcessing) + ; + else if (_rxProcessing) + return false; + + _rxProcessing = true; + return true; +} + +void TpUartDataLinkLayer::isrUnlock() +{ + _rxProcessing = false; +} + +void TpUartDataLinkLayer::clearUartBuffer() +{ + // Clear rx queue + while (_platform.uartAvailable()) + _platform.readUart(); +} + +void TpUartDataLinkLayer::connected(bool state /* = true */) +{ + if (state) + println("TP is connected"); + else + println("TP is disconnected"); + + _connected = state; +} + +void TpUartDataLinkLayer::resetStats() +{ + _rxProcessdFrameCounter = 0; + _rxIgnoredFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxUnkownControlCounter = 0; + _txFrameCounter = 0; + _txProcessdFrameCounter = 0; +} + +bool TpUartDataLinkLayer::reset() +{ + // println("Reset TP"); + if (!_initialized) + { + _platform.setupUart(); + _initialized = true; + } + + // Wait for isr & block isr + isrLock(true); + + // Reset + resetStats(); + clearTxFrame(); + clearTxFrameQueue(); + + if (_rxFrame != nullptr) + { + _rxFrame->reset(); + } + + _rxState = RX_IDLE; + _connected = false; + _stopped = false; + _monitoring = false; + _rxLastTime = 0; + + clearUartBuffer(); + + _platform.writeUart(U_RESET_REQ); + bool success = false; + + const uint32_t start = millis(); + + // During startup answer took up to 2ms and normal 1ms + do + { + const int byte = _platform.readUart(); + + if (byte == -1) + continue; // empty + + if (byte & U_RESET_IND) + { + success = true; + break; // next run for U_CONFIGURE_IND + } + } while (!((millis() - start) >= 10)); + + connected(success); + + if (success) + { + _lastStateRequest = 0; // Force + requestState(true); + _rxLastTime = millis(); + } + + isrUnlock(); + return success; +} + +void TpUartDataLinkLayer::forceAck(bool state) +{ + _forceAck = true; +} + +void TpUartDataLinkLayer::stop(bool state) +{ + if (!_initialized) + return; + + if (state && !_stopped) + _platform.writeUart(U_STOP_MODE_REQ); + else if (!state && _stopped) + _platform.writeUart(U_EXIT_STOP_MODE_REQ); + + _stopped = state; +} + +void TpUartDataLinkLayer::requestBusy(bool state) +{ + if (state && !_busy) + _platform.writeUart(U_SET_BUSY_REQ); + else if (!state && _busy) + _platform.writeUart(U_QUIT_BUSY_REQ); + + _busy = state; +} + +void TpUartDataLinkLayer::monitor() +{ + if (!_initialized || _monitoring) + return; + + // println("busmonitor"); + _monitoring = true; + _platform.writeUart(U_BUSMON_REQ); + resetStats(); +} + +void TpUartDataLinkLayer::enabled(bool value) +{ + // After an unusual device restart, perform a reset, as the TPUart may still be in an incorrect state. + if (!_initialized) + reset(); + + stop(!value); +} + +bool TpUartDataLinkLayer::enabled() const +{ + return _initialized && _connected; +} + +/* + * If a TxFrame has been sent, a confirmation for the transmission is expected. + * However, if there was an invalid frame or bus disconnect, the confirmation is not received and the STack is stuck in the TX_FRAME. + * The wait must therefore be ended after a short waiting time. + */ +void TpUartDataLinkLayer::clearOutdatedTxFrame() +{ + if (_txState == TX_FRAME && (millis() - _txLastTime) > 1000) + processTxFrameComplete(false); +} + +/* + * Here the outgoing frames are taken from the queue and sent. + * This only happens one at a time, as after each frame it is necessary to wait until the frame has come in again and the L_DATA_CON comes in. + * + */ +void TpUartDataLinkLayer::processTxQueue() +{ + if (_txState != TX_IDLE) + return; + + if (_txFrameQueue.front != nullptr) + { + knx_tx_queue_entry_t* entry = _txFrameQueue.front; + _txFrameQueue.front = entry->next; + + if (_txFrameQueue.front == nullptr) + { + _txFrameQueue.back = nullptr; + } + + _txQueueCount--; + + clearTxFrame(); + + // use frame from queue and delete queue entry + _txFrame = entry->frame; + delete entry; + + _txState = TX_FRAME; + _txLastTime = millis(); + +#ifdef DEBUG_TP_FRAMES + print("Outbound: "); + printFrame(_txFrame); + println(); +#endif + + processTxFrameBytes(); + } +} + +/* + * Check whether I have not received any data for too long and set the status to not connected. + * In normal mode, the status is requested every second. A short time can therefore be selected here. + * In monitoring mode there are actual frames, so a longer time is used here. + * Nevertheless, there are suspected disconnects with larger data volumes, so the RxQueue is also taken into account. + */ +void TpUartDataLinkLayer::checkConnected() +{ + if (!isrLock()) + return; + + const uint32_t current = millis(); + + if (_connected) + { + // 5000 instead 3000 because siemens tpuart + const uint32_t timeout = _monitoring ? 10000 : 5000; + + if ((current - _rxLastTime) > timeout) + { + connected(false); + } + } + else + { + if (_rxLastTime > 0 && (current - _rxLastTime) < 1000) + connected(); + } + + isrUnlock(); +} + +void TpUartDataLinkLayer::loop() +{ + if (!_initialized) + return; + + /* + * If an overflow has been detected, change to RX_INVALID. + * However, this only applies in the loop and not in ISR. But when using ISR and DMA, this should never happen. + */ + if (_rxOverflow) + { + println("TPUart overflow detected!"); + _rxOverflow = false; + _rxState = RX_INVALID; + } + + if (_tpState) + { + print("TPUart state error: "); + println(_tpState, 2); + _tpState = 0; + } + + processRx(); +#ifdef USE_TP_RX_QUEUE + processRxQueue(); +#endif + + requestState(); + clearOutdatedTxFrame(); + processTxQueue(); + checkConnected(); +} + +void TpUartDataLinkLayer::rxFrameReceived(TpFrame* tpFrame) +{ + uint8_t* cemiData = tpFrame->cemiData(); + CemiFrame cemiFrame(cemiData, tpFrame->cemiSize()); + // printHex(" TP<: ", tpFrame->data(), tpFrame->size()); + // printHex(" CEMI<: ", cemiFrame.data(), cemiFrame.dataLength()); + +#ifdef KNX_ACTIVITYCALLBACK + + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_RECV << KNX_ACTIVITYCALLBACK_DIR)); + +#endif + + frameReceived(cemiFrame); + free(cemiData); +} + +DptMedium TpUartDataLinkLayer::mediumType() const +{ + return DptMedium::KNX_TP1; +} + +/* + * This can be used to switch the power supply to the V20V (VCC2) + */ +#ifdef NCN5120 +void TpUartDataLinkLayer::powerControl(bool state) +{ + _platform.writeUart(U_INT_REG_WR_REQ_ACR0); + + if (state) + _platform.writeUart(ACR0_FLAG_DC2EN | ACR0_FLAG_V20VEN | ACR0_FLAG_XCLKEN | ACR0_FLAG_V20VCLIMIT); + else + _platform.writeUart(ACR0_FLAG_XCLKEN | ACR0_FLAG_V20VCLIMIT); +} +#endif + +bool TpUartDataLinkLayer::processTxFrameBytes() +{ + // println("processTxFrameBytes"); + + /* + * Each frame must be introduced with a U_L_DATA_START_REQ and each subsequent byte with a further position byte (6bit). + * Since the position byte consists of the U_L_DATA_START_REQ + position and we start with 0 anyway, a further distinction is not necessary. + * distinction is not necessary. + * + * However, the last byte (checksum) uses the U_L_DATA_END_REQ + position! + * In addition, there is another special feature for extended frames up to 263 bytes long, the 6 bits are no longer sufficient. + * Here a U_L_DATA_OFFSET_REQ + Position (3bit) must be prefixed. This means that 9 bits are available for the position. + */ + for (uint16_t i = 0; i < _txFrame->size(); i++) + { + uint8_t offset = (i >> 6); + uint8_t position = (i & 0x3F); + + if (offset) + { + // position++; + _platform.writeUart(U_L_DATA_OFFSET_REQ | offset); + } + + if (i == (_txFrame->size() - 1)) // Last bytes (checksum) + _platform.writeUart(U_L_DATA_END_REQ | position); + else + _platform.writeUart(U_L_DATA_START_REQ | position); + + _platform.writeUart(_txFrame->data(i)); + } + +#ifdef KNX_ACTIVITYCALLBACK + + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_SEND << KNX_ACTIVITYCALLBACK_DIR)); + +#endif + + return true; +} + +TpUartDataLinkLayer::TpUartDataLinkLayer(DeviceObject& devObj, + NetworkLayerEntity& netLayerEntity, + Platform& platform, + ITpUartCallBacks& cb, + DataLinkLayerCallbacks* dllcb) + : DataLinkLayer(devObj, netLayerEntity, platform), + _cb(cb), + _dllcb(dllcb) +{ + _rxFrame = new TpFrame(MAX_KNX_TELEGRAM_SIZE); +} + +/* + * Returns the number of frames that could not be processed. + */ +uint32_t TpUartDataLinkLayer::getRxInvalidFrameCounter() +{ + return _rxInvalidFrameCounter; +} + +/* + * Returns the number of frames that are valid and intended for the device + */ +uint32_t TpUartDataLinkLayer::getRxProcessdFrameCounter() +{ + return _rxProcessdFrameCounter; +} + +/* + * Returns the number of frames that are valid but not intended for the device + */ +uint32_t TpUartDataLinkLayer::getRxIgnoredFrameCounter() +{ + return _rxIgnoredFrameCounter; +} + +/* + * Returns the number of control bytes counted that were not recognized + */ +uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() +{ + return _rxUnkownControlCounter; +} + +/* + * Returns the number of frames sent + */ +uint32_t TpUartDataLinkLayer::getTxFrameCounter() +{ + return _txFrameCounter; +} +/* + * Returns the number of frames sent + */ +uint32_t TpUartDataLinkLayer::getTxProcessedFrameCounter() +{ + return _txProcessdFrameCounter; +} + +bool TpUartDataLinkLayer::isConnected() +{ + return _connected; +} + +bool TpUartDataLinkLayer::isStopped() +{ + return _stopped; +} + +bool TpUartDataLinkLayer::isBusy() +{ + return _busy; +} + +bool TpUartDataLinkLayer::isMonitoring() +{ + return _monitoring; +} + +bool TpUartDataLinkLayer::markerMode() +{ + if (_monitoring) + return false; + +#ifdef NCN5120 + // return true; +#endif + + return false; +} + +void TpUartDataLinkLayer::processRxFrame(TpFrame* tpFrame) +{ + if (_monitoring) + { + print("Monitor: "); + printFrame(tpFrame); + println(); + } + else if (tpFrame->flags() & TP_FRAME_FLAG_INVALID) + { + print("\x1B["); + print(31); + print("m"); + print("Invalid: "); + printFrame(tpFrame); + print("\x1B["); + print(0); + println("m"); + } + else if (tpFrame->flags() & TP_FRAME_FLAG_ADDRESSED) + { +#ifdef DEBUG_TP_FRAMES + print("Inbound: "); + printFrame(tpFrame); + println(); +#endif + + if (!(tpFrame->flags() & TP_FRAME_FLAG_ECHO)) + rxFrameReceived(tpFrame); + } +} + +#ifdef USE_TP_RX_QUEUE +/* + * This method allows the processing of the incoming bytes to be handled additionally via an interrupt (ISR). + * The prerequisite is that the interrupt runs on the same core as the knx.loop! + * + * With an RP2040 where the ISR is also locked when a block is erased, + * processing can be caught up between the erases. This significantly minimizes the risk of frame losses. + */ +void __isr __time_critical_func(TpUartDataLinkLayer::processRxISR)() +{ + processRx(true); +} + +/* + * Puts the received frame into a queue. This queue is necessary, + * because a frame can optionally be received via an ISR and processing must still take place normally in the knx.loop. + * In addition, this queue is statically preallocated, as no malloc etc. can be made in an ISR. + */ +void TpUartDataLinkLayer::pushRxFrameQueue() +{ + if (availableInRxQueue() < (_rxFrame->size() + 3)) + return; + + // Payloadsize (2 byte) + pushByteToRxQueue(_rxFrame->size() & 0xFF); + pushByteToRxQueue(_rxFrame->size() >> 8); + // Paylodflags (1 byte) + pushByteToRxQueue(_rxFrame->flags()); + + for (size_t i = 0; i < _rxFrame->size(); i++) + { + pushByteToRxQueue(_rxFrame->data(i)); + } + + asm volatile("" ::: "memory"); + _rxBufferCount++; +} + +void TpUartDataLinkLayer::processRxQueue() +{ + if (!isrLock()) + return; + + while (_rxBufferCount) + { + const uint16_t size = pullByteFromRxQueue() + (pullByteFromRxQueue() << 8); + TpFrame tpFrame = TpFrame(size); + tpFrame.addFlags(pullByteFromRxQueue()); + + for (uint16_t i = 0; i < size; i++) + tpFrame.addByte(pullByteFromRxQueue()); + + processRxFrame(&tpFrame); + asm volatile("" ::: "memory"); + _rxBufferCount--; + } + + isrUnlock(); +} + +void TpUartDataLinkLayer::pushByteToRxQueue(uint8_t byte) +{ + _rxBuffer[_rxBufferFront] = byte; + _rxBufferFront = (_rxBufferFront + 1) % (MAX_RX_QUEUE_BYTES); +} + +uint8_t TpUartDataLinkLayer::pullByteFromRxQueue() +{ + uint8_t byte = _rxBuffer[_rxBufferRear]; + _rxBufferRear = (_rxBufferRear + 1) % (MAX_RX_QUEUE_BYTES); + return byte; +} + +uint16_t TpUartDataLinkLayer::availableInRxQueue() +{ + return ((_rxBufferFront == _rxBufferRear) ? (MAX_RX_QUEUE_BYTES) : ((((MAX_RX_QUEUE_BYTES) - _rxBufferFront) + _rxBufferRear) % (MAX_RX_QUEUE_BYTES))) - 1; +} +#endif + +#endif \ No newline at end of file diff --git a/components/knx/src/knx/tpuart_data_link_layer.h b/components/knx/src/knx/tpuart_data_link_layer.h new file mode 100644 index 0000000..77fe0ef --- /dev/null +++ b/components/knx/src/knx/tpuart_data_link_layer.h @@ -0,0 +1,180 @@ +#pragma once + +#include "config.h" +#ifdef USE_TP + +#include "data_link_layer.h" +#include "tp_frame.h" +#include + +#define MAX_KNX_TELEGRAM_SIZE 263 + +#ifndef MAX_RX_QUEUE_BYTES + #define MAX_RX_QUEUE_BYTES MAX_KNX_TELEGRAM_SIZE + 50 +#endif + +#ifndef MAX_TX_QUEUE + #define MAX_TX_QUEUE 50 +#endif + +// __time_critical_func fallback +#ifndef ARDUINO_ARCH_RP2040 + #define __time_critical_func(X) X + #define __isr +#endif + +void printFrame(TpFrame* tpframe); + +class ITpUartCallBacks +{ + public: + virtual ~ITpUartCallBacks() = default; + virtual TPAckType isAckRequired(uint16_t address, bool isGrpAddr) = 0; +}; + +class TpUartDataLinkLayer : public DataLinkLayer +{ + using DataLinkLayer::_deviceObject; + using DataLinkLayer::_platform; + + public: + TpUartDataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, + Platform& platform, ITpUartCallBacks& cb, DataLinkLayerCallbacks* dllcb = nullptr); + + void loop(); + void enabled(bool value); + bool enabled() const; + DptMedium mediumType() const override; + bool reset(); + void monitor(); + void stop(bool state); + void requestBusy(bool state); + void forceAck(bool state); + void setRepetitions(uint8_t nack, uint8_t busy); + // Alias + void setFrameRepetition(uint8_t nack, uint8_t busy); + bool isConnected(); + bool isMonitoring(); + bool isStopped(); + bool isBusy(); + void resetStats(); + +#ifdef USE_TP_RX_QUEUE + void processRxISR(); +#endif +#ifdef NCN5120 + void powerControl(bool state); +#endif + + uint32_t getRxInvalidFrameCounter(); + uint32_t getRxProcessdFrameCounter(); + uint32_t getRxIgnoredFrameCounter(); + uint32_t getRxUnknownControlCounter(); + uint32_t getTxFrameCounter(); + uint32_t getTxProcessedFrameCounter(); + uint8_t getMode(); + + private: + // Frame + struct knx_tx_queue_entry_t + { + TpFrame* frame; + knx_tx_queue_entry_t* next = nullptr; + + knx_tx_queue_entry_t(TpFrame* tpFrame) + : frame(tpFrame) + { + } + }; + + // TX Queue + struct knx_tx_queue_t + { + knx_tx_queue_entry_t* front = nullptr; + knx_tx_queue_entry_t* back = nullptr; + } _txFrameQueue; + + TpFrame* _txFrame = nullptr; + TpFrame* _rxFrame = nullptr; + + volatile bool _stopped = false; + volatile bool _connected = false; + volatile bool _monitoring = false; + volatile bool _busy = false; + volatile bool _initialized = false; + + volatile uint8_t _rxState = 0; + volatile uint8_t _txState = 0; + volatile uint32_t _rxProcessdFrameCounter = 0; + volatile uint32_t _rxInvalidFrameCounter = 0; + volatile uint32_t _rxIgnoredFrameCounter = 0; + volatile uint32_t _rxUnkownControlCounter = 0; + volatile uint32_t _txFrameCounter = 0; + volatile uint32_t _txProcessdFrameCounter = 0; + volatile bool _rxMarker = false; + volatile bool _rxOverflow = false; + volatile uint8_t _tpState = 0x0; + volatile uint32_t _txLastTime = 0; + volatile uint32_t _rxLastTime = 0; + volatile bool _forceAck = false; + uint8_t _txQueueCount = 0; + + inline bool markerMode(); + + /* + * bits + * + * 5-7 Busy (Default 11 = 3) + * 0-3 Nack (Default 11 = 3) + */ + volatile uint8_t _repetitions = 0b00110011; + + // to prevent parallel rx processing by isr (when using) + volatile bool _rxProcessing = false; + + volatile uint32_t _lastStateRequest = 0; + + // void loadNextTxFrame(); + inline bool processTxFrameBytes(); + bool sendFrame(CemiFrame& frame); + void rxFrameReceived(TpFrame* frame); + void dataConBytesReceived(uint8_t* buffer, uint16_t length, bool success); + + void processRx(bool isr = false); + void checkConnected(); + void processRxByte(); + void processTxQueue(); + void clearTxFrameQueue(); + void processRxFrameComplete(); + inline void processRxFrame(TpFrame* tpFrame); + void pushTxFrameQueue(TpFrame* tpFrame); + void requestState(bool force = false); + void requestConfig(); + inline void processRxFrameByte(uint8_t byte); + +#ifdef USE_TP_RX_QUEUE + // Es muss ein Extended Frame rein passen + 1Byte je erlaubter ms Verzögerung + volatile uint8_t _rxBuffer[MAX_RX_QUEUE_BYTES] = {}; + volatile uint16_t _rxBufferFront = 0; + volatile uint16_t _rxBufferRear = 0; + volatile uint8_t _rxBufferCount = 0; + + void pushByteToRxQueue(uint8_t byte); + uint8_t pullByteFromRxQueue(); + uint16_t availableInRxQueue(); + void pushRxFrameQueue(); + void processRxQueue(); +#endif + + inline bool isrLock(bool blocking = false); + inline void isrUnlock(); + inline void clearUartBuffer(); + inline void connected(bool state = true); + void clearTxFrame(); + void clearOutdatedTxFrame(); + void processTxFrameComplete(bool success); + + ITpUartCallBacks& _cb; + DataLinkLayerCallbacks* _dllcb; +}; +#endif diff --git a/components/knx/src/knx/transport_layer.cpp b/components/knx/src/knx/transport_layer.cpp new file mode 100644 index 0000000..5515036 --- /dev/null +++ b/components/knx/src/knx/transport_layer.cpp @@ -0,0 +1,799 @@ +#include "transport_layer.h" +#include "apdu.h" +#include "cemi_frame.h" +#include "network_layer.h" +#include "application_layer.h" +#include "platform.h" +#include "bits.h" +#include + +TransportLayer::TransportLayer(ApplicationLayer& layer): _savedFrame(0), + _savedFrameConnecting(0), _applicationLayer(layer) +{ + _currentState = Closed; +} + +void TransportLayer::networkLayer(NetworkLayer& layer) +{ + _networkLayer = &layer; +} + +void TransportLayer::groupAddressTable(AddressTableObject& addrTable) +{ + _groupAddressTable = &addrTable; +} + +void TransportLayer::dataIndividualIndication(uint16_t destination, HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu) +{ + //if (tpdu.apdu().length() > 0) + //{ + // print.print("<- TL "); + // tpdu.printPDU(); + // print.print("<- TL "); + // tpdu.apdu().printPDU(); + //} + + uint8_t sequenceNo = tpdu.sequenceNumber(); + + switch (tpdu.type()) + { + case DataInduvidual: + _applicationLayer.dataIndividualIndication(hopType, priority, source, tpdu.apdu()); + return; + + case DataConnected: + if (source == _connectionAddress) + { + if (sequenceNo == _seqNoRecv) + { + //E4 + switch (_currentState) + { + case Closed: + //A0 nothing + break; + + case OpenIdle: + case OpenWait: + A2(source, priority, tpdu.apdu()); + break; + + case Connecting: + _currentState = Closed; + A6(destination); + break; + } + } + else if (sequenceNo == ((_seqNoRecv - 1) & 0xF)) + { + //E5 + switch (_currentState) + { + case Closed: + //A0 + break; + + case OpenIdle: + case OpenWait: + case Connecting: + A3(source, priority, tpdu); + break; + } + } + else + { + //E6 + switch (_currentState) + { + case Closed: + //A0 + break; + + case OpenIdle: + case OpenWait: + A4(source, priority, tpdu); + break; + + case Connecting: + A6(destination); + break; + } + } + } + else + { + //E7 + switch (_currentState) + { + case Closed: + case OpenIdle: + case OpenWait: + //A0 + break; + + case Connecting: + A10(source); + break; + } + } + + break; + + case Connect: + if (source == _connectionAddress) + { + //E0 + switch (_currentState) + { + case Closed: + _currentState = OpenIdle; + A1(source); + break; + + case OpenWait: + case OpenIdle: + case Connecting: + //A0: do nothing + break; + } + } + else + { + //E1 + switch (_currentState) + { + case Closed: + _currentState = OpenIdle; + A1(source); + break; + + case OpenIdle: + case OpenWait: + case Connecting: + A10(source); + break; + } + } + + break; + + case Disconnect: + if (source == _connectionAddress) + { + //E2 + switch (_currentState) + { + case Closed: + //A0 do nothing + break; + + case OpenIdle: + case OpenWait: + case Connecting: + _currentState = Closed; + A5(source); + break; + + default: + break; + } + } + else + { + //E3 + //A0: do nothing + } + + break; + + case Ack: + if (source == _connectionAddress) + { + if (sequenceNo == _seqNoSend) + { + //E8 + switch (_currentState) + { + case Closed: + case OpenIdle: + //A0 + break; + + case OpenWait: + _currentState = OpenIdle; + A8(); + break; + + case Connecting: + _currentState = Closed; + A6(source); + break; + } + } + else + { + //E9 + switch (_currentState) + { + case Closed: + case OpenIdle: + //A0 + break; + + case OpenWait: + case Connecting: + _currentState = Closed; + A6(source); + break; + } + } + } + else + { + //E10 + switch (_currentState) + { + case Connecting: + A10(source); + break; + + default: /* do nothing */ + break; + } + } + + break; + + case Nack: + if (source == _connectionAddress) + { + if (sequenceNo != _seqNoSend) + { + //E11 + switch (_currentState) + { + case Closed: + case OpenIdle: + case OpenWait: + //A0 + break; + + case Connecting: + _currentState = Closed; + A6(source); + break; + } + } + else + { + if (_repCount < _maxRepCount) + { + //E12 + switch (_currentState) + { + case Closed: + //A0 + break; + + case Connecting: + case OpenIdle: + _currentState = Closed; + A6(source); + break; + + case OpenWait: + A9(); + break; + } + } + else + { + //E13 + switch (_currentState) + { + case Closed: + //A0 + break; + + case OpenIdle: + case OpenWait: + case Connecting: + _currentState = Closed; + A6(source); + break; + } + } + } + } + else + { + //E14 + switch (_currentState) + { + case Closed: + case OpenIdle: + case OpenWait: + //A0 + break; + + case Connecting: + A10(source); + break; + + default: + break; + } + } + + break; + + default: + break; + } +} + +void TransportLayer::dataIndividualConfirm(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu, bool status) +{ + TpduType type = tpdu.type(); + + switch (type) + { + case DataInduvidual: + _applicationLayer.dataIndividualConfirm(ack, hopType, priority, destination, tpdu.apdu(), status); + break; + + case DataConnected: + //E22 + //A0: do nothing + break; + + case Connect: + if (status) + { + //E19 + switch (_currentState) + { + case Closed: + case OpenIdle: + case OpenWait: + //A0: do nothing + break; + + case Connecting: + _currentState = OpenIdle; + A13(destination); + break; + } + } + else + { + //E20 + switch (_currentState) + { + case Closed: + case OpenIdle: + case OpenWait: + //A0: do nothing + break; + + case Connecting: + A5(destination); + break; + } + } + + break; + + case Disconnect: + //E21 + //A0: do nothing + break; + + case Ack: + //E23 + //A0: do nothing + break; + + case Nack: + //E24 + //A0: do nothing + break; + + default: + break; + /* DataGroup and DataBroadcast should not appear here. If they do ignore them. */ + } +} + +void TransportLayer::dataGroupIndication(uint16_t destination, HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu) +{ + if (_groupAddressTable == nullptr) + return; + + uint16_t tsap = _groupAddressTable->getTsap(destination); + + if (tsap == 0) + return; + + _applicationLayer.dataGroupIndication(hopType, priority, tsap, tpdu.apdu()); +} + +void TransportLayer::dataGroupConfirm(AckType ack, uint16_t source, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu, bool status) +{ + _applicationLayer.dataGroupConfirm(ack, hopType, priority, destination, tpdu.apdu(), status); +} + +void TransportLayer::dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu) +{ + _applicationLayer.dataBroadcastIndication(hopType, priority, source, tpdu.apdu()); +} + +void TransportLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu, bool status) +{ + _applicationLayer.dataBroadcastConfirm(ack, hopType, priority, tpdu.apdu(), status); +} + +void TransportLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu) +{ + _applicationLayer.dataSystemBroadcastIndication(hopType, priority, source, tpdu.apdu()); +} + +void TransportLayer::dataSystemBroadcastConfirm(AckType ack, HopCountType hopType, TPDU& tpdu, Priority priority, bool status) +{ + _applicationLayer.dataSystemBroadcastConfirm(hopType, priority, tpdu.apdu(), status); +} + +void TransportLayer::dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu) +{ + if (_groupAddressTable == nullptr) + return; + + uint16_t groupAdress = _groupAddressTable->getGroupAddress(tsap); + TPDU& tpdu = apdu.frame().tpdu(); + _networkLayer->dataGroupRequest(ack, groupAdress, hopType, priority, tpdu); +} + +void TransportLayer::dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu) +{ + TPDU& tpdu = apdu.frame().tpdu(); + _networkLayer->dataBroadcastRequest(ack, hopType, priority, tpdu); +} + +void TransportLayer::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu) +{ + TPDU& tpdu = apdu.frame().tpdu(); + return _networkLayer->dataSystemBroadcastRequest(ack, hopType, priority, tpdu); +} + +void TransportLayer::dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu) +{ + //print.print("-> TL "); + //apdu.printPDU(); + TPDU& tpdu = apdu.frame().tpdu(); + _networkLayer->dataIndividualRequest(ack, destination, hopType, priority, tpdu); +} + +void TransportLayer::connectRequest(uint16_t destination, Priority priority) +{ + //E25 + switch (_currentState) + { + case Closed: + _currentState = Connecting; + A12(destination, priority); + break; + + case OpenIdle: + case OpenWait: + case Connecting: + _currentState = Closed; + A6(destination); + break; + } +} + +void TransportLayer::disconnectRequest(uint16_t tsap, Priority priority) +{ + //E26 + switch (_currentState) + { + case Closed: + A15(priority, tsap); + break; + + case OpenIdle: + case OpenWait: + case Connecting: + _currentState = Closed; + A14(tsap, priority); + break; + } +} + +void TransportLayer::dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu) +{ + //print.print("-> TL "); + //apdu.printPDU(); + //E15 + switch (_currentState) + { + case Closed: + //A0 + break; + + case OpenIdle: + _currentState = OpenWait; + A7(priority, apdu); + break; + + case OpenWait: + case Connecting: + A11(tsap, priority, apdu); + break; + + default: + break; + } +} + +void TransportLayer::connectionTimeoutIndication() +{ + //E16 + switch (_currentState) + { + case Closed: + //A0: do nothing + break; + + case OpenIdle: + case OpenWait: + case Connecting: + _currentState = Closed; + A6(_connectionAddress); + break; + } +} + +void TransportLayer::ackTimeoutIndication() +{ + if (_repCount < _maxRepCount) + { + //E17 + switch (_currentState) + { + case Closed: + case OpenIdle: + case Connecting: + //A0: do nothing + break; + + case OpenWait: + A9(); + break; + } + } + else + { + //E18 + switch (_currentState) + { + case Closed: + case OpenIdle: + case Connecting: + //A0: do nothing + break; + + case OpenWait: + _currentState = Closed; + A6(_connectionAddress); + break; + } + } +} + +// Note: we should probably also add the TSAP as argument if would support multiple concurrent connections +uint8_t TransportLayer::getTpciSeqNum() +{ + // Return seqNum that would be used for sending next frame + // together with the TPDU type. + return ((_seqNoSend & 0xF) << 2); +} + +// Note: we should probably also add the TSAP as argument if would support multiple concurrent connections +uint16_t TransportLayer::getConnectionAddress() +{ + return _connectionAddress; +} + +void TransportLayer::loop() +{ + uint32_t milliseconds = millis(); + + if (_connectionTimeoutEnabled + && (milliseconds - _connectionTimeoutStartMillis) > _connectionTimeoutMillis) + connectionTimeoutIndication(); + + if (_ackTimeoutEnabled + && (milliseconds - _ackTimeoutStartMillis) > _ackTimeoutMillis) + ackTimeoutIndication(); + + if (_savedConnectingValid) + { + //retry saved event + _savedConnectingValid = false; + dataConnectedRequest(_savedTsapConnecting, _savedPriorityConnecting, _savedFrameConnecting.apdu()); + } +} + +void TransportLayer::sendControlTelegram(TpduType pduType, uint8_t seqNo) +{ + CemiFrame frame(0); + TPDU& tpdu = frame.tpdu(); + tpdu.type(pduType); + tpdu.sequenceNumber(seqNo); + _networkLayer->dataIndividualRequest(AckRequested, _connectionAddress, NetworkLayerParameter, + SystemPriority, tpdu); +} + +void TransportLayer::A0() +{ + /* do nothing */ +} + +void TransportLayer::A1(uint16_t source) +{ + _connectionAddress = source; + _applicationLayer.connectIndication(source); + _seqNoSend = 0; + _seqNoRecv = 0; + enableConnectionTimeout(); +} + +void incSeqNr(uint8_t& seqNr) +{ + seqNr += 1; + + if (seqNr > 0xf) + seqNr = 0; +} + +void TransportLayer::A2(uint16_t source, Priority priority, APDU& apdu) +{ + sendControlTelegram(Ack, _seqNoRecv); + incSeqNr(_seqNoRecv); + _applicationLayer.dataConnectedIndication(priority, source, apdu); + enableConnectionTimeout(); +} + +void TransportLayer::A3(uint16_t source, Priority priority, TPDU& recTpdu) +{ + sendControlTelegram(Ack, recTpdu.sequenceNumber()); + enableConnectionTimeout(); +} + +void TransportLayer::A4(uint16_t source, Priority priority, TPDU& recTpdu) +{ + sendControlTelegram(Nack, recTpdu.sequenceNumber()); + enableConnectionTimeout(); +} + +void TransportLayer::A5(uint16_t tsap) +{ + _applicationLayer.disconnectIndication(tsap); + disableConnectionTimeout(); + disableAckTimeout(); +} + +void TransportLayer::A6(uint16_t tsap) +{ + sendControlTelegram(Disconnect, 0); + _applicationLayer.disconnectIndication(tsap); + disableConnectionTimeout(); + disableAckTimeout(); +} + +void TransportLayer::A7(Priority priority, APDU& apdu) +{ + _savedPriority = priority; + TPDU& tpdu = apdu.frame().tpdu(); + tpdu.type(DataConnected); + tpdu.sequenceNumber(_seqNoSend); + _savedFrame = apdu.frame(); + _networkLayer->dataIndividualRequest(AckRequested, _connectionAddress, NetworkLayerParameter, priority, tpdu); + _repCount = 0; + enableAckTimeout(); + enableConnectionTimeout(); +} + +void TransportLayer::A8() +{ + disableAckTimeout(); + incSeqNr(_seqNoSend); + _applicationLayer.dataConnectedConfirm(0); + enableConnectionTimeout(); +} + +void TransportLayer::A9() +{ + TPDU& tpdu = _savedFrame.tpdu(); + // tpdu is still initialized from last send + _networkLayer->dataIndividualRequest(AckRequested, _connectionAddress, NetworkLayerParameter, _savedPriority, tpdu); + _repCount += 1; + enableAckTimeout(); + enableConnectionTimeout(); +} + +void TransportLayer::A10(uint16_t source) +{ + CemiFrame frame(0); + TPDU& tpdu = frame.tpdu(); + tpdu.type(Disconnect); + tpdu.sequenceNumber(0); + _networkLayer->dataIndividualRequest(AckRequested, source, NetworkLayerParameter, SystemPriority, tpdu); +} + +void TransportLayer::A11(uint16_t tsap, Priority priority, APDU& apdu) +{ + _savedTsapConnecting = tsap; + _savedPriorityConnecting = priority; + _savedFrameConnecting = apdu.frame(); + _savedConnectingValid = true; +} + +void TransportLayer::A12(uint16_t destination, Priority priority) +{ + _connectionAddress = destination; + CemiFrame frame(0); + TPDU& tpdu = frame.tpdu(); + tpdu.type(Connect); + _networkLayer->dataIndividualRequest(AckRequested, destination, NetworkLayerParameter, priority, tpdu); + _seqNoRecv = 0; + _seqNoSend = 0; + enableConnectionTimeout(); +} + +void TransportLayer::A13(uint16_t destination) +{ + _applicationLayer.connectConfirm(destination, 0, true); +} + +void TransportLayer::A14(uint16_t tsap, Priority priority) +{ + CemiFrame frame(0); + TPDU& tpdu = frame.tpdu(); + tpdu.type(Disconnect); + tpdu.sequenceNumber(0); + _networkLayer->dataIndividualRequest(AckRequested, _connectionAddress, NetworkLayerParameter, SystemPriority, tpdu); + _applicationLayer.disconnectConfirm(priority, tsap, true); + disableConnectionTimeout(); + disableAckTimeout(); +} + +void TransportLayer::A15(Priority priority, uint16_t tsap) +{ + _applicationLayer.disconnectConfirm(priority, tsap, true); + disableConnectionTimeout(); + disableAckTimeout(); +} + +void TransportLayer::enableConnectionTimeout() +{ + _connectionTimeoutStartMillis = millis(); + _connectionTimeoutEnabled = true; +} + +void TransportLayer::disableConnectionTimeout() +{ + _connectionTimeoutEnabled = false; +} + +void TransportLayer::enableAckTimeout() +{ + _ackTimeoutStartMillis = millis(); + _ackTimeoutEnabled = true; +} + +void TransportLayer::disableAckTimeout() +{ + _ackTimeoutEnabled = false; +} diff --git a/components/knx/src/knx/transport_layer.h b/components/knx/src/knx/transport_layer.h new file mode 100644 index 0000000..a74d9b5 --- /dev/null +++ b/components/knx/src/knx/transport_layer.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include "knx_types.h" +#include "tpdu.h" +#include "address_table_object.h" +#include "cemi_frame.h" + +class ApplicationLayer; +class APDU; +class NetworkLayer; +class Platform; + +enum StateType { Closed, OpenIdle, OpenWait, Connecting }; + +class TransportLayer +{ + public: + TransportLayer(ApplicationLayer& layer); + void networkLayer(NetworkLayer& layer); + void groupAddressTable(AddressTableObject& addrTable); + + #pragma region from network layer + void dataIndividualIndication(uint16_t destination, HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu); + void dataIndividualConfirm(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu, bool status); + void dataGroupIndication(uint16_t destination, HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu); + void dataGroupConfirm(AckType ack, uint16_t source, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu, bool status); + void dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu); + void dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu, bool status); + void dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, TPDU& tpdu); + void dataSystemBroadcastConfirm(AckType ack, HopCountType hopType, TPDU& tpdu, Priority priority, bool status); + #pragma endregion + + #pragma region from application layer + /** + * Request to send an APDU that via multicast. See 3.2 of @cite knx:3/3/4. + * See also ApplicationLayer::dataGroupConfirm and ApplicationLayer::dataGroupIndication. + * This method is called by the ApplicationLayer. + * + * @param tsap used the find the correct GroupObject with the help of the AssociationTableObject. + * See 3.1.1 of @cite knx:3/3/7 + * + * @param apdu The submitted APDU. + * + * @param priority The ::Priority of the request. + * + * @param hopType Should routing be endless or should the NetworkLayer::hopCount be used? See also ::HopCountType. + * + * @param ack Did we want a DataLinkLayer acknowledgement? See ::AckType. + */ + void dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu); + void dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu); + void dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu); + void dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu); + + void connectRequest(uint16_t destination, Priority priority); + void disconnectRequest(uint16_t tsap, Priority priority); + // apdu must be valid until it was confirmed + void dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu); + + uint8_t getTpciSeqNum(); + uint16_t getConnectionAddress(); + #pragma endregion + + #pragma region other + void connectionTimeoutIndication(); + void ackTimeoutIndication(); + void loop(); + #pragma endregion + + private: + #pragma region States + Priority _savedPriority = LowPriority; + CemiFrame _savedFrame; + Priority _savedPriorityConnecting; + CemiFrame _savedFrameConnecting; + uint16_t _savedTsapConnecting; + bool _savedConnectingValid = false; + enum StateEvent + { + E0, E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, + E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27 + }; + StateType _currentState = Closed; + void sendControlTelegram(TpduType pduType, uint8_t seqNo); + void A0(); + void A1(uint16_t source); + void A2(uint16_t source, Priority priority, APDU& apdu); + void A3(uint16_t source, Priority priority, TPDU& recTpdu); + void A4(uint16_t source, Priority priority, TPDU& recTpdu); + void A5(uint16_t source); + void A6(uint16_t source); + void A7(Priority priority, APDU& apdu); + void A8(); + void A9(); + void A10(uint16_t source); + void A11(uint16_t tsap, Priority priority, APDU& apdu); + void A12(uint16_t destination, Priority priority); + void A13(uint16_t destination); + void A14(uint16_t destination, Priority priority); + void A15(Priority priority, uint16_t tsap); + void enableConnectionTimeout(); + void disableConnectionTimeout(); + void enableAckTimeout(); + void disableAckTimeout(); + uint16_t _connectionAddress = 0; + uint8_t _seqNoSend = 0; + uint8_t _seqNoRecv = 0; + bool _connectionTimeoutEnabled = false; + uint32_t _connectionTimeoutStartMillis = 0; + uint16_t _connectionTimeoutMillis = 6000; + bool _ackTimeoutEnabled = false; + uint32_t _ackTimeoutStartMillis = 0; + uint16_t _ackTimeoutMillis = 3000; + uint8_t _repCount = 0; + uint8_t _maxRepCount = 3; + #pragma endregion + ApplicationLayer& _applicationLayer; + AddressTableObject* _groupAddressTable; + NetworkLayer* _networkLayer; +}; diff --git a/components/knx/src/knx/usb_tunnel_interface.cpp b/components/knx/src/knx/usb_tunnel_interface.cpp new file mode 100644 index 0000000..3eb02c0 --- /dev/null +++ b/components/knx/src/knx/usb_tunnel_interface.cpp @@ -0,0 +1,565 @@ +#include "config.h" +#ifdef USE_USB + +#include "bits.h" +#include "usb_tunnel_interface.h" +#include "cemi_server.h" +#include "cemi_frame.h" + +#include +#include + +#define MIN(a, b) ((a < b) ? (a) : (b)) + +#define MAX_EP_SIZE 64 +#define HID_HEADER_SIZE 3 +#define MAX_KNX_TELEGRAM_SIZE 263 +#define KNX_HID_REPORT_ID 0x01 +#define PROTOCOL_VERSION 0x00 +#define PROTOCOL_HEADER_LENGTH 0x08 + +// Maximum possible payload data bytes in a transfer protocol body +#define MAX_DATASIZE_START_PACKET 52 +#define MAX_DATASIZE_PARTIAL_PACKET 61 + +#define PACKET_TYPE_START 1 +#define PACKET_TYPE_END 2 +#define PACKET_TYPE_PARTIAL 4 + +//#define DEBUG_TX_HID_REPORT +//#define DEBUG_RX_HID_REPORT + +extern bool sendHidReport(uint8_t* data, uint16_t length); +extern bool isSendHidReportPossible(); + +// class UsbTunnelInterface + +UsbTunnelInterface::UsbTunnelInterface(CemiServer& cemiServer, + uint16_t mId, + uint16_t mV) + : _cemiServer(cemiServer), + _manufacturerId(mId), + _maskVersion(mV) +{ +} + +void UsbTunnelInterface::loop() +{ + // Make sure that the USB HW is also ready to send another report + if (!isTxQueueEmpty() && isSendHidReportPossible()) + { + uint8_t* buffer; + uint16_t length; + loadNextTxFrame(&buffer, &length); + sendHidReport(buffer, length); + delete buffer; + } + + // Check if we already a COMPLETE transport protocol packet + // A transport protocol packet might be split into multiple HID reports and + // need to be assembled again + if (rxHaveCompletePacket) + { + handleHidReportRxQueue(); + rxHaveCompletePacket = false; + } +} + +/* USB TX */ + +void UsbTunnelInterface::sendCemiFrame(CemiFrame& frame) +{ + sendKnxHidReport(KnxTunneling, ServiceIdNotUsed, frame.data(), frame.dataLength()); +} + +void UsbTunnelInterface::addBufferTxQueue(uint8_t* data, uint16_t length) +{ + _queue_buffer_t* tx_buffer = new _queue_buffer_t; + + tx_buffer->length = MAX_EP_SIZE; + tx_buffer->data = new uint8_t[MAX_EP_SIZE]; // We always have to send full max. USB endpoint size of 64 bytes + tx_buffer->next = nullptr; + + memcpy(tx_buffer->data, data, tx_buffer->length); + memset(&tx_buffer->data[length], 0x00, MAX_EP_SIZE - length); // Set unused bytes to zero + + if (_tx_queue.back == nullptr) + { + _tx_queue.front = _tx_queue.back = tx_buffer; + } + else + { + _tx_queue.back->next = tx_buffer; + _tx_queue.back = tx_buffer; + } +} + +bool UsbTunnelInterface::isTxQueueEmpty() +{ + if (_tx_queue.front == nullptr) + { + return true; + } + + return false; +} + +void UsbTunnelInterface::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength) +{ + if (_tx_queue.front == nullptr) + { + return; + } + + _queue_buffer_t* tx_buffer = _tx_queue.front; + *sendBuffer = tx_buffer->data; + *sendBufferLength = tx_buffer->length; + _tx_queue.front = tx_buffer->next; + + if (_tx_queue.front == nullptr) + { + _tx_queue.back = nullptr; + } + + delete tx_buffer; + +#ifdef DEBUG_TX_HID_REPORT + print("TX HID report: len: "); + // We do not print the padded zeros + uint8_t len = (*sendBuffer)[2]; + println(len, DEC); + + for (int i = 0; i < len; i++) + { + if ((*sendBuffer)[i] < 16) + print("0"); + + print((*sendBuffer)[i], HEX); + print(" "); + } + + println(""); +#endif +} + +void UsbTunnelInterface::sendKnxHidReport(ProtocolIdType protId, ServiceIdType servId, uint8_t* data, uint16_t length) +{ + uint16_t maxData = MAX_DATASIZE_START_PACKET; + uint8_t packetType = PACKET_TYPE_START; + + if (length > maxData) + { + packetType |= PACKET_TYPE_PARTIAL; + } + + uint16_t offset = 0; + uint8_t* buffer = nullptr; + + // In theory we can only have sequence numbers from 1..5 + // First packet: 51 bytes max + // Other packets: 62 bytes max. + // -> 51 + 4*62 = 296 bytes -> enough for a KNX cEMI extended frame APDU + Transport Protocol Header length + for (uint8_t seqNum = 1; seqNum < 6; seqNum++) + { + uint16_t copyLen = MIN(length, maxData); + + // If this is the first packet we include the transport protocol header + if (packetType & PACKET_TYPE_START) + { + buffer = new uint8_t[copyLen + 8 + HID_HEADER_SIZE]; // length of transport protocol header: 11 bytes + buffer[2] = 8 + copyLen; // KNX USB Transfer Protocol Body length + buffer[3] = PROTOCOL_VERSION; // Protocol version (fixed 0x00) + buffer[4] = PROTOCOL_HEADER_LENGTH; // USB KNX Transfer Protocol Header Length (fixed 0x08) + pushWord(copyLen, &buffer[5]); // KNX USB Transfer Protocol Body length (e.g. cEMI length) + buffer[7] = (uint8_t) protId; // KNX Tunneling (0x01) or KNX Bus Access Server (0x0f) + buffer[8] = (protId == KnxTunneling) ? (uint8_t)CEMI : (uint8_t)servId; // either EMI ID or Service Id + buffer[9] = 0x00; // Manufacturer (fixed 0x00) see KNX Spec 9/3 p.23 3.4.1.3.5 + buffer[10] = 0x00; // Manufacturer (fixed 0x00) see KNX Spec 9/3 p.23 3.4.1.3.5 + memcpy(&buffer[11], &data[offset], copyLen); // Copy payload for KNX USB Transfer Protocol Body + } + else + { + buffer = new uint8_t[copyLen]; // no transport protocol header in partial packets + buffer[2] = copyLen; // KNX USB Transfer Protocol Body length + memcpy(&buffer[0], &data[offset], copyLen); // Copy payload for KNX USB Transfer Protocol Body + } + + offset += copyLen; + + if (offset >= length) + { + packetType |= PACKET_TYPE_END; + } + + buffer[0] = KNX_HID_REPORT_ID; // ReportID (fixed 0x01) + buffer[1] = ((seqNum << 4) & 0xF0) | (packetType & 0x07); // PacketInfo (SeqNo and Type) + + addBufferTxQueue(buffer, (buffer[2] + HID_HEADER_SIZE)); + + delete[] buffer; + + if (offset >= length) + { + break; + } + + packetType &= ~PACKET_TYPE_START; + maxData = MAX_DATASIZE_PARTIAL_PACKET; + } +} + +/* USB RX */ + +// Invoked when received SET_REPORT control request or via interrupt out pipe +void UsbTunnelInterface::receiveHidReport(uint8_t const* data, uint16_t bufSize) +{ + // Check KNX ReportID (fixed 0x01) + if (data[0] == KNX_HID_REPORT_ID) + { + // We just store only the used space of the HID report buffer + // which is normally padded with 0 to fill the complete USB EP size (e.g. 64 bytes) + uint8_t packetLength = data[2] + HID_HEADER_SIZE; + UsbTunnelInterface::addBufferRxQueue(data, packetLength); + + // Check if packet type indicates last packet + if ((data[1] & PACKET_TYPE_END) == PACKET_TYPE_END) + { + // Signal main loop that we have a complete KNX USB packet + rxHaveCompletePacket = true; + } + } +} + +UsbTunnelInterface::_queue_t UsbTunnelInterface::_rx_queue; +bool UsbTunnelInterface::rxHaveCompletePacket = false; + +void UsbTunnelInterface::addBufferRxQueue(const uint8_t* data, uint16_t length) +{ + _queue_buffer_t* rx_buffer = new _queue_buffer_t; + + rx_buffer->length = length; + rx_buffer->data = new uint8_t[rx_buffer->length]; + rx_buffer->next = nullptr; + + memcpy(rx_buffer->data, data, rx_buffer->length); + + if (_rx_queue.back == nullptr) + { + _rx_queue.front = _rx_queue.back = rx_buffer; + } + else + { + _rx_queue.back->next = rx_buffer; + _rx_queue.back = rx_buffer; + } +} + +bool UsbTunnelInterface::isRxQueueEmpty() +{ + if (_rx_queue.front == nullptr) + { + return true; + } + + return false; +} + +void UsbTunnelInterface::loadNextRxBuffer(uint8_t** receiveBuffer, uint16_t* receiveBufferLength) +{ + if (_rx_queue.front == nullptr) + { + return; + } + + _queue_buffer_t* rx_buffer = _rx_queue.front; + *receiveBuffer = rx_buffer->data; + *receiveBufferLength = rx_buffer->length; + _rx_queue.front = rx_buffer->next; + + if (_rx_queue.front == nullptr) + { + _rx_queue.back = nullptr; + } + + delete rx_buffer; + +#ifdef DEBUG_RX_HID_REPORT + print("RX HID report: len: "); + println(*receiveBufferLength, DEC); + + for (int i = 0; i < (*receiveBufferLength); i++) + { + if ((*receiveBuffer)[i] < 16) + print("0"); + + print((*receiveBuffer)[i], HEX); + print(" "); + } + + println(""); +#endif +} + +void UsbTunnelInterface::handleTransferProtocolPacket(uint8_t* data, uint16_t length) +{ + if (data[0] == PROTOCOL_VERSION && // Protocol version (fixed 0x00) + data[1] == PROTOCOL_HEADER_LENGTH) // USB KNX Transfer Protocol Header Length (fixed 0x08) + { + uint16_t bodyLength; + popWord(bodyLength, (uint8_t*)&data[2]); // KNX USB Transfer Protocol Body length + + if (data[4] == (uint8_t) BusAccessServer) // Bus Access Server Feature (0x0F) + { + handleBusAccessServerProtocol((ServiceIdType)data[5], &data[8], bodyLength); + } + else if (data[4] == (uint8_t) KnxTunneling) // KNX Tunneling (0x01) + { + if (data[5] == (uint8_t) CEMI) // EMI type: only cEMI supported (0x03)) + { + // Prepare the cEMI frame + CemiFrame frame((uint8_t*)&data[8], bodyLength); + /* + print("cEMI USB RX len: "); + print(length); + + print(" data: "); + printHex(" data: ", buffer, length); + */ + _cemiServer.frameReceived(frame); + } + else + { + println("Error: Only cEMI is supported!"); + } + } + } +} + +void UsbTunnelInterface::handleHidReportRxQueue() +{ + if (isRxQueueEmpty()) + { + println("Error: RX HID report queue was empty!"); + return; + } + + uint8_t tpPacket[MAX_KNX_TELEGRAM_SIZE + PROTOCOL_HEADER_LENGTH]; // Transport Protocol Header + Body + uint16_t offset = 0; + bool success = false; + + // Now we have to reassemble the whole transport protocol packet which might be distributed over multiple HID reports + + // In theory we can only have sequence numbers from 1..5 + // First packet: 51 bytes max + // Other packets: 62 bytes max. + // -> 51 + 4*62 = 296 bytes -> enough for a KNX cEMI extended frame APDU + Transport Protocol Header length + for (int expSeqNum = 1; expSeqNum < 6; expSeqNum++) + { + // We should have at least one packet: either single packet (START and END set) or + // start packet (START and PARTIAL set) -> thus load first part + uint8_t* data; + uint16_t bufSize; + loadNextRxBuffer(&data, &bufSize); // bufSize contains the complete HID report length incl. HID header + + // Get KNX HID report header details + uint8_t seqNum = data[1] >> 4; + uint8_t packetType = data[1] & 0x07; + uint8_t packetLength = MIN(data[2], bufSize - HID_HEADER_SIZE); // Do not try to read more than we actually have! + + // Does the received sequence number match the expected one? + if (expSeqNum != seqNum) + { + println("Error: Wrong sequence number!"); + delete data; + continue; + } + + // first RX buffer from queue should contain the first part of the transfer protocol packet + if ((expSeqNum == 1) && ((packetType & PACKET_TYPE_START) != PACKET_TYPE_START)) + { + println("Error: Sequence number 1 does not contain a START packet!"); + delete data; + continue; + } + + // Make sure we only have one START packet + if ((expSeqNum != 1) && ((packetType & PACKET_TYPE_START) == PACKET_TYPE_START)) + { + println("Error: Sequence number (!=1) contains a START packet!"); + delete data; + continue; + } + + // Make sure other packets are marked correctly as PARTIAL packet + if ((expSeqNum != 1) && ((packetType & PACKET_TYPE_PARTIAL) != PACKET_TYPE_PARTIAL)) + { + println("Error: Sequence number (!=1) must be a PARTIAL packet!"); + delete data; + continue; + } + + // Not really necessary, but we reset the offset here to zero + if ((packetType & PACKET_TYPE_START) == PACKET_TYPE_START) + { + offset = 0; + } + + // Copy KNX HID Report Body to final buffer for concatenating + memcpy(&tpPacket[offset], &data[3], packetLength); + // Remove the source HID report buffer + delete data; + // Move offset + offset += packetLength; + + // If we reached the end of the transport protocol packet, leave the loop + if ((packetType & PACKET_TYPE_END) == PACKET_TYPE_END) + { + success = true; + break; + } + } + + // Make sure that we really saw the end of the transport protocol packet + if (success) + { + handleTransferProtocolPacket(tpPacket, offset); + } + else + { + println("Error: Did not find END packet!"); + } +} + +void UsbTunnelInterface::handleBusAccessServerProtocol(ServiceIdType servId, const uint8_t* requestData, uint16_t packetLength) +{ + uint8_t respData[3]; // max. 3 bytes are required for a response + + switch (servId) + { + case DeviceFeatureGet: // Device Feature Get + { + FeatureIdType featureId = (FeatureIdType)requestData[0]; + respData[0] = (uint8_t) featureId; // first byte in repsonse is the featureId itself again + + switch (featureId) + { + case SupportedEmiType: // Supported EMI types + println("Device Feature Get: Supported EMI types"); + respData[1] = 0x00; // USB KNX Transfer Protocol Body: Feature Data + respData[2] = 0x04; // USB KNX Transfer Protocol Body: Feature Data -> only cEMI supported + sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3); + break; + + case HostDeviceDescriptorType0: // Host Device Descriptor Type 0 + println("Device Feature Get: Host Device Descriptor Type 0"); + pushWord(_maskVersion, &respData[1]); // USB KNX Transfer Protocol Body: Feature Data -> Mask version + sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3); + break; + + case BusConnectionStatus: // Bus connection status + println("Device Feature Get: Bus connection status"); + respData[1] = 1; // USB KNX Transfer Protocol Body: Feature Data -> bus connection status + sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 2); + break; + + case KnxManufacturerCode: // KNX manufacturer code + println("Device Feature Get: KNX manufacturer code"); + pushWord(_manufacturerId, &respData[1]); // USB KNX Transfer Protocol Body: Feature Data -> Manufacturer Code + sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3); + break; + + case ActiveEmiType: // Active EMI type + println("Device Feature Get: Active EMI type"); + respData[1] = (uint8_t) CEMI; // USB KNX Transfer Protocol Body: Feature Data -> cEMI type ID + sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 2); + break; + + default: + break; + } + + break; + } + + case DeviceFeatureSet: // Device Feature Set + { + FeatureIdType featureId = (FeatureIdType)requestData[0]; + + switch (featureId) + { + case ActiveEmiType: // Active EMI type + print("Device Feature Set: Active EMI type: "); + + if (requestData[1] < 16) + print("0"); + + println(requestData[1], HEX); // USB KNX Transfer Protocol Body: Feature Data -> EMI TYPE ID + break; + + // All other featureIds must not be set + case SupportedEmiType: // Supported EMI types + case HostDeviceDescriptorType0: // Host Device Descriptor Type 0 + case BusConnectionStatus: // Bus connection status + case KnxManufacturerCode: // KNX manufacturer code + default: + break; + } + + break; + } + + // These are only sent from the device to the host + case DeviceFeatureResponse: // Device Feature Response + case DeviceFeatureInfo: // Device Feature Info + case DeviceFeatureEscape: // reserved (ESCAPE for future extensions) + default: + break; + } +} + +/* USB HID report descriptor for KNX HID */ + +const uint8_t UsbTunnelInterface::descHidReport[] = +{ + //TUD_HID_REPORT_DESC_KNXHID_INOUT(64) + 0x06, 0xA0, 0xFF, // Usage Page (Vendor Defined 0xFFA0) + 0x09, 0x01, // Usage (0x01) + 0xA1, 0x01, // Collection (Application) + 0x09, 0x01, // Usage (0x01) + 0xA1, 0x00, // Collection (Physical) + 0x06, 0xA1, 0xFF, // Usage Page (Vendor Defined 0xFFA1) + 0x09, 0x03, // Usage (0x03) + 0x09, 0x04, // Usage (0x04) + 0x15, 0x80, // Logical Minimum (-128) + 0x25, 0x7F, // Logical Maximum (127) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0xFF, // Physical Maximum (-1) + 0x75, 0x08, // Report Size (8) + 0x85, 0x01, // Report ID (1) + 0x95, 0x3F, // Report Count (63) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x05, // Usage (0x05) + 0x09, 0x06, // Usage (0x06) + 0x15, 0x80, // Logical Minimum (-128) + 0x25, 0x7F, // Logical Maximum (127) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0xFF, // Physical Maximum (-1) + 0x75, 0x08, // Report Size (8) + 0x85, 0x01, // Report ID (1) + 0x95, 0x3F, // Report Count (63) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xC0 // End Collection +}; + +const uint8_t* UsbTunnelInterface::getKnxHidReportDescriptor() +{ + return &descHidReport[0]; +} + +uint16_t UsbTunnelInterface::getHidReportDescriptorLength() +{ + return sizeof(descHidReport); +} + +#endif diff --git a/components/knx/src/knx/usb_tunnel_interface.h b/components/knx/src/knx/usb_tunnel_interface.h new file mode 100644 index 0000000..0395b02 --- /dev/null +++ b/components/knx/src/knx/usb_tunnel_interface.h @@ -0,0 +1,96 @@ +#pragma once + +#include "config.h" +#ifdef USE_USB +#include + +class CemiServer; +class CemiFrame; + +enum ProtocolIdType +{ + KnxTunneling = 0x01, + BusAccessServer = 0x0f +}; + +enum EmiIdType +{ + EmiIdNotUsed = 0x00, + EMI1 = 0x01, + EMI2 = 0x02, + CEMI = 0x03 +}; + +enum ServiceIdType +{ + ServiceIdNotUsed = 0x00, + DeviceFeatureGet = 0x01, + DeviceFeatureResponse = 0x02, + DeviceFeatureSet = 0x03, + DeviceFeatureInfo = 0x04, + DeviceFeatureEscape = 0xFF +}; + +enum FeatureIdType +{ + SupportedEmiType = 0x01, + HostDeviceDescriptorType0 = 0x02, + BusConnectionStatus = 0x03, + KnxManufacturerCode = 0x04, + ActiveEmiType = 0x05 +}; + +class UsbTunnelInterface +{ + public: + UsbTunnelInterface(CemiServer& cemiServer, uint16_t manufacturerId, uint16_t maskVersion); + + void loop(); + + // from cEMI server + void sendCemiFrame(CemiFrame& frame); + + static const uint8_t* getKnxHidReportDescriptor(); + static uint16_t getHidReportDescriptorLength(); + static void receiveHidReport(uint8_t const* data, uint16_t bufSize); + + private: + struct _queue_buffer_t + { + uint8_t* data; + uint16_t length; + _queue_buffer_t* next; + }; + + struct _queue_t + { + _queue_buffer_t* front = nullptr; + _queue_buffer_t* back = nullptr; + }; + + static const uint8_t descHidReport[]; + + CemiServer& _cemiServer; + + uint16_t _manufacturerId; + uint16_t _maskVersion; + + // USB TX queue + _queue_t _tx_queue; + void addBufferTxQueue(uint8_t* data, uint16_t length); + bool isTxQueueEmpty(); + void loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength); + + // USB RX queue + static _queue_t _rx_queue; + static void addBufferRxQueue(const uint8_t* data, uint16_t length); + bool isRxQueueEmpty(); + void loadNextRxBuffer(uint8_t** receiveBuffer, uint16_t* receiveBufferLength); + static bool rxHaveCompletePacket; + + void handleTransferProtocolPacket(uint8_t* data, uint16_t length); + void handleHidReportRxQueue(); + void handleBusAccessServerProtocol(ServiceIdType servId, const uint8_t* requestData, uint16_t packetLength); + void sendKnxHidReport(ProtocolIdType protId, ServiceIdType servId, uint8_t* data, uint16_t length); +}; +#endif \ No newline at end of file diff --git a/components/knx/src/knx_facade.cpp b/components/knx/src/knx_facade.cpp new file mode 100644 index 0000000..6704b99 --- /dev/null +++ b/components/knx/src/knx_facade.cpp @@ -0,0 +1,133 @@ +#include "knx_facade.h" + +#include "knx/bits.h" + +#ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + +#if (defined(ARDUINO_ARCH_STM32) || \ + defined(ARDUINO_ARCH_ESP32) || \ + defined(ARDUINO_ARCH_ESP8266) || \ + defined(ARDUINO_ARCH_SAMD) || \ + defined(ARDUINO_ARCH_RP2040)) || \ + defined(LIBRETINY) + +// Only ESP8266 and ESP32 have this define. For all other platforms this is just empty. +#ifndef IRAM_ATTR + #define IRAM_ATTR +#endif + +#ifndef PROG_BTN_PRESS_MIN_MILLIS + #define PROG_BTN_PRESS_MIN_MILLIS 50 +#endif + +#ifndef PROG_BTN_PRESS_MAX_MILLIS + #define PROG_BTN_PRESS_MAX_MILLIS 500 +#endif + + +IRAM_ATTR void buttonEvent() +{ + static uint32_t lastEvent = 0; + static uint32_t lastPressed = 0; + + uint32_t diff = millis() - lastEvent; + + if (diff >= PROG_BTN_PRESS_MIN_MILLIS && diff <= PROG_BTN_PRESS_MAX_MILLIS) + { + if (millis() - lastPressed > 200) + { + knx.toggleProgMode(); + lastPressed = millis(); + } + } + + lastEvent = millis(); +} +#endif + +#ifdef ARDUINO_ARCH_SAMD + // predefined global instance for TP or RF or TP/RF coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x27B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x2920 + KnxFacade knx(buttonEvent); + #else + #error "Mask version not supported on ARDUINO_ARCH_SAMD" + #endif +#elif defined(ARDUINO_ARCH_RP2040) + // predefined global instance for TP or RF or IP or TP/RF coupler or TP/IP coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x27B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x57B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x2920 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x091A + KnxFacade knx(buttonEvent); + #else + #error "Mask version not supported on ARDUINO_ARCH_RP2040" + #endif + +#elif defined(ARDUINO_ARCH_ESP8266) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x57B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x091A + KnxFacade knx(buttonEvent); + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP8266" + #endif + +#elif defined(ARDUINO_ARCH_ESP32) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x57B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x091A + KnxFacade knx(buttonEvent); + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP32" + #endif + +#elif defined(LIBRETINY) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x57B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x091A + KnxFacade knx(buttonEvent); + #else + #error "Mask version not supported on LIBRETINY" + #endif + +#elif defined(ESP_PLATFORM) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x57B0 + KnxFacade knx(buttonEvent); + #elif MASK_VERSION == 0x091A + KnxFacade knx(buttonEvent); + #else + #error "Mask version not supported on ESP_IDF_ESP32" + #endif + +#elif defined(ARDUINO_ARCH_STM32) + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonEvent); + #else + #error "Mask version not supported on ARDUINO_ARCH_STM32" + #endif +#else // Non-Arduino platforms and Linux platform + // no predefined global instance +#endif + +#endif // KNX_NO_AUTOMATIC_GLOBAL_INSTANCE diff --git a/components/knx/src/knx_facade.h b/components/knx/src/knx_facade.h new file mode 100644 index 0000000..185ba64 --- /dev/null +++ b/components/knx/src/knx_facade.h @@ -0,0 +1,589 @@ +#pragma once + +#include "knx/bits.h" +#include "knx/config.h" +#include "knx/bau07B0.h" +#include "knx/bau091A.h" +#include "knx/bau27B0.h" +#include "knx/bau2920.h" +#include "knx/bau57B0.h" + +#ifndef USERDATA_SAVE_SIZE + #define USERDATA_SAVE_SIZE 0 +#endif + +#ifdef ARDUINO_ARCH_SAMD + #include "samd_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif +#elif defined(ARDUINO_ARCH_RP2040) + #include "rp2040_arduino_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif +#elif defined(ARDUINO_ARCH_ESP8266) + #include "esp_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif +#elif defined(ARDUINO_ARCH_ESP32) + #include "esp32_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif +#elif defined(LIBRETINY) + #include "libretiny_platform.h" +#elif defined(ESP_PLATFORM) + #include "esp32_idf_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif +#elif defined(ARDUINO_ARCH_STM32) + #include "stm32_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif +#elif __linux__ + #include "linux_platform.h" +#else + #include "cc1310_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + extern void buttonUp(); + #endif +#endif + +#ifndef KNX_LED + #define KNX_LED -1 +#endif +#ifndef KNX_LED_ACTIVE_ON + #define KNX_LED_ACTIVE_ON 0 +#endif +#ifndef KNX_BUTTON + #define KNX_BUTTON -1 +#endif + +typedef const uint8_t* (*RestoreCallback)(const uint8_t* buffer); +typedef uint8_t* (*SaveCallback)(uint8_t* buffer); +typedef void (*IsrFunctionPtr)(); +typedef void (*ProgLedOnCallback)(); +typedef void (*ProgLedOffCallback)(); +#ifdef KNX_ACTIVITYCALLBACK + typedef void (*ActivityCallback)(uint8_t info); +#endif + +template class KnxFacade : private SaveRestore +{ + public: + KnxFacade() : _platformPtr(new P()), _bauPtr(new B(*_platformPtr)), _bau(*_bauPtr) + { + manufacturerId(0xfa); + bauNumber(platform().uniqueSerialNumber()); + _bau.addSaveRestore(this); + } + + KnxFacade(B& bau) : _bau(bau) + { + _platformPtr = static_cast(&bau.platform()); + manufacturerId(0xfa); + bauNumber(platform().uniqueSerialNumber()); + _bau.addSaveRestore(this); + } + + KnxFacade(IsrFunctionPtr buttonISRFunction) : _platformPtr(new P()), _bauPtr(new B(*_platformPtr)), _bau(*_bauPtr) + { + manufacturerId(0xfa); + bauNumber(platform().uniqueSerialNumber()); + _bau.addSaveRestore(this); + setButtonISRFunction(buttonISRFunction); + } + + virtual ~KnxFacade() + { + if (_bauPtr) + delete _bauPtr; + + if (_platformPtr) + delete _platformPtr; + } + + P& platform() + { + return *_platformPtr; + } + + B& bau() + { + return _bau; + } + + bool enabled() + { + return _bau.enabled(); + } + + void enabled(bool value) + { + _bau.enabled(value); + } + + bool progMode() + { + return _bau.deviceObject().progMode(); + } + + void progMode(bool value) + { + _bau.deviceObject().progMode(value); + } + + /** + * To be called by ISR handling on button press. + */ + void toggleProgMode() + { + _toggleProgMode = true; + } + + bool configured() + { + return _bau.configured(); + } + + /** + * returns HIGH if led is active on HIGH, LOW otherwise + */ + uint32_t ledPinActiveOn() + { + return _ledPinActiveOn; + } + + /** + * Sets if the programming led is active on HIGH or LOW. + * + * Set to HIGH for GPIO--RESISTOR--LED--GND or to LOW for GPIO--LED--RESISTOR--VDD + */ + void ledPinActiveOn(uint32_t value) + { + _ledPinActiveOn = value; + } + + int32_t ledPin() + { + return _ledPin; + } + + void ledPin(int32_t value) + { + _ledPin = value; + } + + void setProgLedOffCallback(ProgLedOffCallback progLedOffCallback) + { + _progLedOffCallback = progLedOffCallback; + } + + void setProgLedOnCallback(ProgLedOnCallback progLedOnCallback) + { + _progLedOnCallback = progLedOnCallback; + } + + int32_t buttonPin() + { + return _buttonPin; + } + + void buttonPin(int32_t value) + { + _buttonPin = value; + } + + void readMemory() + { + _bau.readMemory(); + } + + void writeMemory() + { + _bau.writeMemory(); + } + + uint16_t individualAddress() + { + return _bau.deviceObject().individualAddress(); + } + + void loop() + { + if (progMode() != _progLedState) + { + _progLedState = progMode(); + + if (_progLedState) + { + println("progmode on"); + progLedOn(); + } + else + { + println("progmode off"); + progLedOff(); + } + } + + if (_toggleProgMode) + { + progMode(!progMode()); + _toggleProgMode = false; + } + + _bau.loop(); + } + + void manufacturerId(uint16_t value) + { + _bau.deviceObject().manufacturerId(value); + } + + void bauNumber(uint32_t value) + { + _bau.deviceObject().bauNumber(value); + } + + void orderNumber(const uint8_t* value) + { + _bau.deviceObject().orderNumber(value); + } + + void hardwareType(const uint8_t* value) + { + _bau.deviceObject().hardwareType(value); + } + + void version(uint16_t value) + { + _bau.deviceObject().version(value); + } + + void start() + { + if (_ledPin >= 0) + { +#if defined(ESP_PLATFORM) + gpio_reset_pin((gpio_num_t)ledPin()); + gpio_set_direction((gpio_num_t)ledPin(), GPIO_MODE_OUTPUT); +#else + pinMode(_ledPin, OUTPUT); +#endif // ESP_PLATFORM + } + + progLedOff(); + + if(_buttonPin >= 0) + { +#if defined(ESP_PLATFORM) + if (_progButtonISRFuncPtr) + { + attachInterrupt(_buttonPin, _progButtonISRFuncPtr, CHANGE); + } +#else + pinMode(_buttonPin, INPUT_PULLUP); + + if (_progButtonISRFuncPtr) + { + // Workaround for https://github.com/arduino/ArduinoCore-samd/issues/587 +#if (ARDUINO_API_VERSION >= 10200) + attachInterrupt(_buttonPin, _progButtonISRFuncPtr, (PinStatus)CHANGE); +#else + attachInterrupt(_buttonPin, _progButtonISRFuncPtr, CHANGE); +#endif // ARDUINO_API_VERSION + } +#endif // ESP_PLATFORM + } + + enabled(true); + } + + void setButtonISRFunction(IsrFunctionPtr progButtonISRFuncPtr) + { + _progButtonISRFuncPtr = progButtonISRFuncPtr; + } + + void setSaveCallback(SaveCallback func) + { + _saveCallback = func; + } + + void setRestoreCallback(RestoreCallback func) + { + _restoreCallback = func; + } + + uint8_t* paramData(uint32_t addr) + { + if (!_bau.configured()) + return nullptr; + + return _bau.parameters().data(addr); + } + + // paramBit(address, shift) + // get state of a parameter as a boolean like "enable/disable", ... + // Declaration in XML file: + // ... + // + // + // + // + // + // + // ... + // + // + // + // + // + // + // + // + // + // ... + // Usage in code : + // if ( knx.paramBit(1,1)) + // { + // //do somthings .... + // } + bool paramBit(uint32_t addr, uint8_t shift) + { + if (!_bau.configured()) + return 0; + + return (bool) ((_bau.parameters().getByte(addr) >> (7 - shift)) & 0x01); + } + + uint8_t paramByte(uint32_t addr) + { + if (!_bau.configured()) + return 0; + + return _bau.parameters().getByte(addr); + } + + // Same usage than paramByte(addresse) for signed parameters + // Declaration in XML file + // + // + // + int8_t paramSignedByte(uint32_t addr) + { + if (!_bau.configured()) + return 0; + + return (int8_t) _bau.parameters().getByte(addr); + } + + uint16_t paramWord(uint32_t addr) + { + if (!_bau.configured()) + return 0; + + return _bau.parameters().getWord(addr); + } + + uint32_t paramInt(uint32_t addr) + { + if (!_bau.configured()) + return 0; + + return _bau.parameters().getInt(addr); + } + + double paramFloat(uint32_t addr, ParameterFloatEncodings enc) + { + if (!_bau.configured()) + return 0; + + return _bau.parameters().getFloat(addr, enc); + } + +#if (MASK_VERSION == 0x07B0) || (MASK_VERSION == 0x27B0) || (MASK_VERSION == 0x57B0) + GroupObject& getGroupObject(uint16_t goNr) + { + return _bau.groupObjectTable().get(goNr); + } +#endif + + void restart(uint16_t individualAddress) + { + SecurityControl sc = {false, None}; + _bau.restartRequest(individualAddress, sc); + } + + void beforeRestartCallback(BeforeRestartCallback func) + { + _bau.beforeRestartCallback(func); + } + + BeforeRestartCallback beforeRestartCallback() + { + return _bau.beforeRestartCallback(); + } + + private: + P* _platformPtr = 0; + B* _bauPtr = 0; + B& _bau; + ProgLedOnCallback _progLedOnCallback = 0; + ProgLedOffCallback _progLedOffCallback = 0; +#ifdef KNX_ACTIVITYCALLBACK + ActivityCallback _activityCallback = 0; +#endif + uint32_t _ledPinActiveOn = KNX_LED_ACTIVE_ON; + int32_t _ledPin = KNX_LED; + int32_t _buttonPin = KNX_BUTTON; + SaveCallback _saveCallback = 0; + RestoreCallback _restoreCallback = 0; + volatile bool _toggleProgMode = false; + bool _progLedState = false; + uint16_t _saveSize = USERDATA_SAVE_SIZE; + IsrFunctionPtr _progButtonISRFuncPtr = 0; + + uint8_t* save(uint8_t* buffer) + { + if (_saveCallback != 0) + return _saveCallback(buffer); + + return buffer; + } + + const uint8_t* restore(const uint8_t* buffer) + { + if (_restoreCallback != 0) + return _restoreCallback(buffer); + + return buffer; + } + + uint16_t saveSize() + { + return _saveSize; + } + + void saveSize(uint16_t size) + { + _saveSize = size; + } + + void progLedOn() + { + if (_ledPin >= 0) + { +#if defined(ESP_PLATFORM) + gpio_set_level((gpio_num_t)ledPin(), _ledPinActiveOn); +#else + digitalWrite(_ledPin, _ledPinActiveOn); +#endif // ESP_PLATFORM + } + + if (_progLedOffCallback != 0) + _progLedOnCallback(); + } + + void progLedOff() + { + if (_ledPin >= 0) + { +#if defined(ESP_PLATFORM) + gpio_set_level((gpio_num_t)ledPin(), 1 - _ledPinActiveOn); +#else + digitalWrite(_ledPin, HIGH - _ledPinActiveOn); +#endif // ESP_PLATFORM + } + + if (_progLedOffCallback != 0) + _progLedOffCallback(); + } +}; + +#ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + #ifdef ARDUINO_ARCH_SAMD + // predefined global instance for TP or RF or TP/RF coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x27B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x2920 + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_SAMD" + #endif + #elif defined(ARDUINO_ARCH_RP2040) + // predefined global instance for TP or RF or TP/RF or TP/IP coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x27B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x57B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x2920 + extern KnxFacade knx; + #elif MASK_VERSION == 0x091A + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_RP2040" + #endif + #elif defined(ARDUINO_ARCH_ESP8266) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x57B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x091A + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP8266" + #endif + #elif defined(ARDUINO_ARCH_ESP32) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x57B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x091A + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP32" + #endif + #elif defined(LIBRETINY) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x57B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x091A + extern KnxFacade knx; + #else + #error "Mask version not supported on LIBRETINY" + #endif + #elif defined(ESP_PLATFORM) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x57B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x091A + extern KnxFacade knx; + #else + #error "Mask version not supported on ESP_PLATFORM" + #endif + #elif defined(ARDUINO_ARCH_STM32) + // predefined global instance for TP only + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_STM32" + #endif + #else // Non-Arduino platforms and Linux platform + // no predefined global instance + #endif +#endif // KNX_NO_AUTOMATIC_GLOBAL_INSTANCE diff --git a/components/knx/src/libretiny_platform.cpp b/components/knx/src/libretiny_platform.cpp new file mode 100644 index 0000000..f0a67f6 --- /dev/null +++ b/components/knx/src/libretiny_platform.cpp @@ -0,0 +1,173 @@ +#include "libretiny_platform.h" + +#ifdef LIBRETINY +#include +#include "knx/bits.h" + +#include + +#ifndef KNX_SERIAL +#define KNX_SERIAL Serial +#endif + +#ifndef KNX_FLASH_OFFSET +#error "KNX_FLASH_OFFSET is not defined. E.g. 0x1DB000 for BK7231N" +#elif (KNX_FLASH_OFFSET % 4096) != 0 +#error "KNX_FLASH_OFFSET must be a multiple of 4096" +#endif + +static uint8_t NVS_buffer[KNX_FLASH_SIZE]; + +LibretinyPlatform::LibretinyPlatform() +#ifndef KNX_NO_DEFAULT_UART + : ArduinoPlatform(&KNX_SERIAL) +#endif +{ + _memoryType = Flash; +} + +LibretinyPlatform::LibretinyPlatform( HardwareSerial* s) : ArduinoPlatform(s) +{ + _memoryType = Flash; +} + +uint32_t LibretinyPlatform::currentIpAddress() +{ + return WiFi.localIP(); +} + +uint32_t LibretinyPlatform::currentSubnetMask() +{ + return WiFi.subnetMask(); +} + +uint32_t LibretinyPlatform::currentDefaultGateway() +{ + return WiFi.gatewayIP(); +} + +void LibretinyPlatform::macAddress(uint8_t * addr) +{ + WiFi.macAddress(addr); +} + +uint32_t LibretinyPlatform::uniqueSerialNumber() +{ + return lt_cpu_get_mac_id(); +} + +void LibretinyPlatform::restart() +{ + println("restart"); + lt_reboot(); +} + +void LibretinyPlatform::setupMultiCast(uint32_t addr, uint16_t port) +{ + //workaround for libretiny bug: NETIF_FLAG_IGMP is not set by default + struct netif *netif; + for (netif = netif_list; netif != NULL; netif = netif->next) + { + netif->flags |= NETIF_FLAG_IGMP; + } + + IPAddress mcastaddr(htonl(addr)); + KNX_DEBUG_SERIAL.printf("setup multicast addr: %d.%d.%d.%d port: %d ip: %d.%d.%d.%d\n", mcastaddr[0], mcastaddr[1], mcastaddr[2], mcastaddr[3], port, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); + uint8_t result = _udp.beginMulticast(mcastaddr, port); + KNX_DEBUG_SERIAL.printf("multicast setup result %d\n", result); +} + +void LibretinyPlatform::closeMultiCast() +{ + _udp.stop(); +} + +bool LibretinyPlatform::sendBytesMultiCast(uint8_t * buffer, uint16_t len) +{ + _udp.beginMulticastPacket(); + _udp.write(buffer, len); + _udp.endPacket(); + return true; +} + +int LibretinyPlatform::readBytesMultiCast(uint8_t * buffer, uint16_t maxLen) +{ + int len = _udp.parsePacket(); + + if (len == 0) + return 0; + + if (len > maxLen) + { + KNX_DEBUG_SERIAL.printf("udp buffer to small. was %d, needed %d\n", maxLen, len); + fatalError(); + } + + _udp.read(buffer, len); + return len; +} + +bool LibretinyPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) +{ + IPAddress ucastaddr(htonl(addr)); + println("sendBytesUniCast endPacket fail"); + + if(_udp.beginPacket(ucastaddr, port) == 1) + { + _udp.write(buffer, len); + + if(_udp.endPacket() == 0) + println("sendBytesUniCast endPacket fail"); + } + else + println("sendBytesUniCast beginPacket fail"); + + return true; +} + +size_t LibretinyPlatform::flashEraseBlockSize() +{ + return 16; +} + +size_t LibretinyPlatform::flashPageSize() +{ + return 256; +} + +uint8_t* LibretinyPlatform::userFlashStart() +{ + lt_flash_read(KNX_FLASH_OFFSET, NVS_buffer, KNX_FLASH_SIZE); + return NVS_buffer; +} + +size_t LibretinyPlatform::userFlashSizeEraseBlocks() +{ + if(KNX_FLASH_SIZE <= 0) + return 0; + else + return ( (KNX_FLASH_SIZE - 1) / (flashPageSize() * flashEraseBlockSize())) + 1; +} + +void LibretinyPlatform::flashErase(uint16_t eraseBlockNum) +{ + // 16 pages x 256byte/page = 4096byte + lt_flash_erase_block(KNX_FLASH_OFFSET + eraseBlockNum * flashPageSize() * flashEraseBlockSize()); +} + +void LibretinyPlatform::flashWritePage(uint16_t pageNumber, uint8_t* data) +{ + lt_flash_write(KNX_FLASH_OFFSET + pageNumber * flashPageSize(), data, flashPageSize()); +} + +void LibretinyPlatform::writeBufferedEraseBlock() +{ + if(_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) + { + lt_flash_erase_block(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize()); + lt_flash_write(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), _eraseblockBuffer, flashPageSize() * flashEraseBlockSize()); + _bufferedEraseblockDirty = false; + } +} + +#endif diff --git a/components/knx/src/libretiny_platform.h b/components/knx/src/libretiny_platform.h new file mode 100644 index 0000000..dc62547 --- /dev/null +++ b/components/knx/src/libretiny_platform.h @@ -0,0 +1,53 @@ +#ifdef LIBRETINY +#include "arduino_platform.h" + +#include +#include + +class LibretinyPlatform : public ArduinoPlatform +{ + public: + LibretinyPlatform(); + LibretinyPlatform(HardwareSerial* s); + + // ip stuff + uint32_t currentIpAddress() override; + uint32_t currentSubnetMask() override; + uint32_t currentDefaultGateway() override; + void macAddress(uint8_t* addr) override; + + // unique serial number + uint32_t uniqueSerialNumber() override; + + // basic stuff + void restart(); + + //multicast + void setupMultiCast(uint32_t addr, uint16_t port) override; + void closeMultiCast() override; + bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override; + + //unicast + bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; + + // size of one EraseBlock in pages + virtual size_t flashEraseBlockSize(); + // size of one flash page in bytes + virtual size_t flashPageSize(); + // start of user flash aligned to start of an erase block + virtual uint8_t* userFlashStart(); + // size of the user flash in EraseBlocks + virtual size_t userFlashSizeEraseBlocks(); + // relativ to userFlashStart + virtual void flashErase(uint16_t eraseBlockNum); + // write a single page to flash (pageNumber relative to userFashStart + virtual void flashWritePage(uint16_t pageNumber, uint8_t* data); + + // writes _eraseblockBuffer to flash - overrides Plattform::writeBufferedEraseBlock() for performance optimization only + void writeBufferedEraseBlock(); + private: + WiFiUDP _udp; +}; + +#endif diff --git a/components/knx/src/linux_platform.cpp b/components/knx/src/linux_platform.cpp new file mode 100644 index 0000000..633cf8a --- /dev/null +++ b/components/knx/src/linux_platform.cpp @@ -0,0 +1,1232 @@ +#include "linux_platform.h" +#ifdef __linux__ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // Needed for SPI port +#include // Needed for SPI port +#include // Needed for GPIO edge detection +#include // Needed for delayMicroseconds() + +#include "knx/device_object.h" +#include "knx/address_table_object.h" +#include "knx/association_table_object.h" +#include "knx/group_object_table_object.h" +#include "knx/application_program_object.h" +#include "knx/ip_parameter_object.h" +#include "knx/bits.h" +#include "knx/ip_host_protocol_address_information.h" + +#define MAX_MEM 4096 + +LinuxPlatform::LinuxPlatform() +{ + int socketMac = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + if (socketMac < 0) + { + printf("Lookup socket creation failed"); + return; + } + + struct ifreq ifr; + + struct ifconf ifc; + + char buf[1024]; + + ifc.ifc_len = sizeof(buf); + + ifc.ifc_buf = buf; + + if (ioctl(socketMac, SIOCGIFCONF, &ifc) < 0) + return; + + struct ifreq* it = ifc.ifc_req; + const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq)); + + for (; it != end; ++it) + { + strcpy(ifr.ifr_name, it->ifr_name); + + if (ioctl(socketMac, SIOCGIFFLAGS, &ifr)) + continue; + + if (ifr.ifr_flags & IFF_LOOPBACK) // don't count loopback + continue; + + if (ioctl(socketMac, SIOCGIFHWADDR, &ifr)) + continue; + + if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) + continue; + + memcpy(_macAddress, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN); + + ioctl(socketMac, SIOCGIFADDR, &ifr); + + struct sockaddr_in* ipaddr = (struct sockaddr_in*)&ifr.ifr_addr; + _ipAddress = ntohl(ipaddr->sin_addr.s_addr); + + //printf("IP address: %s\n", inet_ntoa(ipaddr->sin_addr)); + ioctl(socketMac, SIOCGIFNETMASK, &ifr); + struct sockaddr_in* netmask = (struct sockaddr_in*)&ifr.ifr_netmask; + _netmask = ntohl(netmask->sin_addr.s_addr); + //printf("Netmask: %s\n", inet_ntoa(ipaddr->sin_addr)); + break; + } + + close(socketMac); + + // default GW + FILE* f; + char line[100], *p, *c, *g, *saveptr; + + f = fopen("/proc/net/route", "r"); + + while (fgets(line, 100, f)) + { + p = strtok_r(line, " \t", &saveptr); + c = strtok_r(NULL, " \t", &saveptr); + g = strtok_r(NULL, " \t", &saveptr); + + if (p != NULL && c != NULL) + { + if (strcmp(c, "00000000") == 0) + { + //printf("Default interface is : %s \n" , p); + if (g) + { + char* pEnd; + _defaultGateway = ntohl(strtol(g, &pEnd, 16)); + } + + break; + } + } + } + + fclose(f); +} + +LinuxPlatform::~LinuxPlatform() +{ + delete[] _args; +} + +uint32_t millis() +{ + struct timespec spec; + + clock_gettime(CLOCK_MONOTONIC, &spec); + return spec.tv_sec * 1000 + round(spec.tv_nsec / 1.0e6); +} + +void delay(uint32_t millis) +{ + struct timespec ts; + ts.tv_sec = millis / 1000; + ts.tv_nsec = (millis % 1000) * 1000000; + nanosleep(&ts, NULL); +} + +void LinuxPlatform::restart() +{ + execv(_args[0], _args); +} + +void LinuxPlatform::fatalError() +{ + printf("A fatal error occured. Stopping.\n"); + + while (true) + sleep(1); +} + +void LinuxPlatform::setupMultiCast(uint32_t addr, uint16_t port) +{ + if (_multicastSocketFd >= 0) + closeMultiCast(); + + _multicastAddr = addr; + _multicastPort = port; + + struct ip_mreq command; + uint32_t loop = 1; + + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(port); + + _multicastSocketFd = socket(AF_INET, SOCK_DGRAM, 0); + + if (_multicastSocketFd == -1) + { + perror("socket()"); + fatalError(); + } + + /* Mehr Prozessen erlauben, denselben Port zu nutzen */ + loop = 1; + + if (setsockopt(_multicastSocketFd, SOL_SOCKET, SO_REUSEADDR, &loop, sizeof(loop)) < 0) + { + perror("setsockopt:SO_REUSEADDR"); + fatalError(); + } + + if (bind(_multicastSocketFd, (struct sockaddr*)&sin, sizeof(sin)) < 0) + { + perror("bind"); + fatalError(); + } + + /* loopback */ + loop = 0; + + if (setsockopt(_multicastSocketFd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) + { + perror("setsockopt:IP_MULTICAST_LOOP"); + fatalError(); + } + + /* Join the broadcast group: */ + command.imr_multiaddr.s_addr = htonl(addr); + command.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(_multicastSocketFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &command, sizeof(command)) < 0) + { + perror("setsockopt:IP_ADD_MEMBERSHIP"); + fatalError(); + } + + uint32_t flags = fcntl(_multicastSocketFd, F_GETFL); + flags |= O_NONBLOCK; + fcntl(_multicastSocketFd, F_SETFL, flags); +} + +void LinuxPlatform::closeMultiCast() +{ + struct ip_mreq command; + command.imr_multiaddr.s_addr = htonl(_multicastAddr); + command.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(_multicastSocketFd, + IPPROTO_IP, + IP_DROP_MEMBERSHIP, + &command, sizeof(command)) < 0) + { + perror("setsockopt:IP_DROP_MEMBERSHIP"); + } + + close(_multicastSocketFd); + _multicastSocketFd = -1; +} + +bool LinuxPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) +{ + struct sockaddr_in address = {0}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(_multicastAddr); + address.sin_port = htons(_multicastPort); + + ssize_t retVal = 0; + + do + { + retVal = sendto(_multicastSocketFd, buffer, len, 0, (struct sockaddr*)&address, sizeof(address)); + + if (retVal == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + return false; + } + } while (retVal == -1); + + // printHex("<-", buffer, len); + return true; +} + +int LinuxPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) +{ + uint32_t sin_len; + struct sockaddr_in sin; + + sin_len = sizeof(sin); + ssize_t len = recvfrom(_multicastSocketFd, buffer, maxLen, 0, (struct sockaddr*)&sin, &sin_len); + // if (len > 0) + // printHex("->", buffer, len); + + return len; +} + +uint8_t* LinuxPlatform::getEepromBuffer(uint32_t size) +{ + if (_fd < 0) + doMemoryMapping(); + + return _mappedFile + 2; +} + +void LinuxPlatform::commitToEeprom() +{ + if (_fd < 0) + doMemoryMapping(); + + fsync(_fd); +} + +#define FLASHSIZE 0x10000 +void LinuxPlatform::doMemoryMapping() +{ + _fd = open(_flashFilePath.c_str(), O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH); + + if (_fd < 0) + { + puts("Error in file opening"); + //exit(-1); + } + + struct stat st; + + uint32_t ret = fstat(_fd, &st); + + if (ret < 0) + { + puts("Error in fstat"); + //exit(-1); + } + + size_t len_file = st.st_size; + + if (len_file < FLASHSIZE) + { + if (ftruncate(_fd, FLASHSIZE) != 0) + { + puts("Error extending file"); + //exit(-1); + } + + len_file = FLASHSIZE; + } + + unsigned char* addr = (unsigned char*)mmap(NULL, len_file, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0); + + if (addr[0] != 0xAF || addr[1] != 0xFE) + { + memset(addr, 0, FLASHSIZE); + addr[0] = 0xAF; + addr[1] = 0xFE; + } + + if (addr == MAP_FAILED) + { + puts("Error in mmap"); + //exit(-1); + } + + _mappedFile = addr; +} + +void LinuxPlatform::closeSpi() +{ + close(_spiFd); + printf("SPI device closed.\r\n"); +} + +int LinuxPlatform::readWriteSpi(uint8_t* data, size_t len) +{ + uint16_t spiDelay = 0; + uint32_t spiSpeed = 8000000; // 4 MHz SPI speed + uint8_t spiBPW = 8; // Bits per word + + struct spi_ioc_transfer spi; + + // Mentioned in spidev.h but not used in the original kernel documentation + // test program )-: + + memset(&spi, 0, sizeof(spi)); + + spi.tx_buf = (uint64_t)data; + spi.rx_buf = (uint64_t)data; + spi.len = len; + spi.delay_usecs = spiDelay; + spi.speed_hz = spiSpeed; + spi.bits_per_word = spiBPW; + + return ioctl(_spiFd, SPI_IOC_MESSAGE(1), &spi); +} + +void LinuxPlatform::setupSpi() +{ + if ((_spiFd = open("/dev/spidev0.0", O_RDWR)) < 0) + { + printf("ERROR: SPI setup failed! Could not open SPI device!\r\n"); + return; + } + + // Set SPI parameters. + int mode = 0; // Mode 0 + uint8_t spiBPW = 8; // Bits per word + int speed = 8000000; // 4 MHz SPI speed + + if (ioctl(_spiFd, SPI_IOC_WR_MODE, &mode) < 0) + { + printf("ERROR: SPI Mode Change failure: %s\n", strerror(errno)); + close(_spiFd); + return; + } + + if (ioctl(_spiFd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0) + { + printf("ERROR: SPI BPW Change failure: %s\n", strerror(errno)); + close(_spiFd); + return; + } + + if (ioctl(_spiFd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) + { + printf("ERROR: SPI Speed Change failure: %s\n", strerror(errno)); + close(_spiFd); + return; + } + + printf("SPI device setup ok.\r\n"); +} + +void LinuxPlatform::flashFilePath(const std::string path) +{ + _flashFilePath = path; +} + +std::string LinuxPlatform::flashFilePath() +{ + return _flashFilePath; +} + + + +size_t LinuxPlatform::readBytesUart(uint8_t* buffer, size_t length) +{ + return read(_uartFd, buffer, length); +} + +int LinuxPlatform::readUart() +{ + uint8_t x ; + + if (read(_uartFd, &x, 1) != 1) + { + return -1; + } + + return ((int)x) & 0xFF ; +} + +size_t LinuxPlatform::writeUart(const uint8_t* buffer, size_t size) +{ + return write(_uartFd, buffer, size) ; +} + +size_t LinuxPlatform::writeUart(const uint8_t data) +{ + return write(_uartFd, &data, 1) ; +} + +int LinuxPlatform::uartAvailable() +{ + int result ; + + if (ioctl(_uartFd, FIONREAD, &result) == -1) + { + return -1; + } + + return result ; +} + +void LinuxPlatform::closeUart() +{ + if (_uartFd >= 0) + { + close(_uartFd); + } +} + +void LinuxPlatform::setupUart() +{ + /* + * 19200,8E1, no handshake + */ + struct termios options; /* Schnittstellenoptionen */ + + /* Port oeffnen - read/write, kein "controlling tty", Status von DCD ignorieren */ + _uartFd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY); + + if (_uartFd >= 0) + { + /* get the current options */ + fcntl(_uartFd, F_SETFL, 0); + + if (tcgetattr(_uartFd, &options) != 0) + { + close(_uartFd); + _uartFd = -1; + return; + } + + memset(&options, 0, sizeof(options)); /* Structur loeschen, ggf. vorher sichern + und bei Programmende wieder restaurieren */ + /* Baudrate setzen */ + cfsetispeed(&options, B19200); + cfsetospeed(&options, B19200); + + /* setze Optionen */ + options.c_cflag |= PARENB; /* Enable Paritybit */ + options.c_cflag &= ~PARODD; /* Even parity */ + options.c_cflag &= ~CSTOPB; /* 1 Stoppbit */ + options.c_cflag &= ~CSIZE; /* 8 Datenbits */ + options.c_cflag |= CS8; + + /* 19200 bps, 8 Datenbits, CD-Signal ignorieren, Lesen erlauben */ + options.c_cflag |= (CLOCAL | CREAD); + + /* Kein Echo, keine Steuerzeichen, keine Interrupts */ + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_iflag = IGNPAR; /* Parity-Fehler ignorieren */ + options.c_oflag &= ~OPOST; /* setze "raw" Input */ + options.c_cc[VMIN] = 0; /* warten auf min. 0 Zeichen */ + options.c_cc[VTIME] = 10; /* Timeout 1 Sekunde */ + tcflush(_uartFd, TCIOFLUSH); /* Puffer leeren */ + + if (tcsetattr(_uartFd, TCSAFLUSH, &options) != 0) + { + close(_uartFd); + _uartFd = -1; + return; + } + } +} + +#ifndef KNX_NO_PRINT +void printUint64(uint64_t value, int base = DEC) +{ + char buf[8 * sizeof(uint64_t) + 1]; + char* str = &buf[sizeof(buf) - 1]; + *str = '\0'; + + uint64_t n = value; + + do + { + char c = n % base; + n /= base; + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while (n > 0); + + print(str); +} + +void print(const char* s) +{ + printf("%s", s); +} +void print(char c) +{ + printf("%c", c); +} + +void print(unsigned char num) +{ + print(num, DEC); +} + +void print(unsigned char num, int base) +{ + if (base == HEX) + printf("%X", num); + else + printf("%d", num); +} + +void print(int num) +{ + print(num, DEC); +} + +void print(int num, int base) +{ + if (base == HEX) + printf("%X", num); + else + printf("%d", num); +} + +void print(unsigned int num) +{ + print(num, DEC); +} + +void print(unsigned int num, int base) +{ + if (base == HEX) + printf("%X", num); + else + printf("%d", num); +} + +void print(long num) +{ + print(num, DEC); +} + +void print(long num, int base) +{ + if (base == HEX) + printf("%lX", num); + else + printf("%ld", num); +} + +void print(unsigned long num) +{ + print(num, DEC); +} + +void print(unsigned long num, int base) +{ + if (base == HEX) + printf("%lX", num); + else + printf("%ld", num); +} + +void print(unsigned long long num) +{ + printUint64(num); +} + +void print(unsigned long long num, int base) +{ + printUint64(num, base); +} + +void print(double num) +{ + printf("%f", num); +} + +void println(const char* s) +{ + printf("%s\n", s); +} +void println(char c) +{ + printf("%c\n", c); +} + +void println(unsigned char num) +{ + println(num, DEC); +} + +void println(unsigned char num, int base) +{ + if (base == HEX) + printf("%X\n", num); + else + printf("%d\n", num); +} + +void println(int num) +{ + println(num, DEC); +} + +void println(int num, int base) +{ + if (base == HEX) + printf("%X\n", num); + else + printf("%d\n", num); +} + +void println(unsigned int num) +{ + println(num, DEC); +} + +void println(unsigned int num, int base) +{ + if (base == HEX) + printf("%X\n", num); + else + printf("%d\n", num); +} + +void println(long num) +{ + println(num, DEC); +} + +void println(long num, int base) +{ + if (base == HEX) + printf("%lX\n", num); + else + printf("%ld\n", num); +} + +void println(unsigned long num) +{ + println(num, DEC); +} + +void println(unsigned long num, int base) +{ + if (base == HEX) + printf("%lX\n", num); + else + printf("%ld\n", num); +} + +void println(unsigned long long num) +{ + printUint64(num); + println(""); +} + +void println(unsigned long long num, int base) +{ + printUint64(num, base); + println(""); +} + +void println(double num) +{ + printf("%f\n", num); +} + +void println(double num, int places) +{ + printf("%f\n", num); +} + +void println(void) +{ + printf("\n"); +} +#endif // KNX_NO_PRINT + +void pinMode(uint32_t dwPin, uint32_t dwMode) +{ + gpio_export(dwPin); + gpio_direction(dwPin, dwMode); +} + +void digitalWrite(uint32_t dwPin, uint32_t dwVal) +{ + gpio_write(dwPin, dwVal); +} + +uint32_t digitalRead(uint32_t dwPin) +{ + return gpio_read(dwPin); +} + +typedef void (*voidFuncPtr)(void); +void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) +{ +} + +void LinuxPlatform::cmdLineArgs(int argc, char** argv) +{ + if (_args) + delete[] _args; + + _args = new char* [argc + 1]; + memcpy(_args, argv, argc * sizeof(char*)); + _args[argc] = 0; +} + +/* Buffer size for string operations (e.g. snprintf())*/ +#define MAX_STRBUF_SIZE 100 +#define MAX_NUM_GPIO 64 + +static int gpioFds[MAX_NUM_GPIO] = +{ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + }; + +/* Activate GPIO-Pin + * Write GPIO pin number to /sys/class/gpio/export + * Result: 0 = success, -1 = error + */ +int gpio_export(int pin) +{ + char buffer[MAX_STRBUF_SIZE]; /* Output Buffer */ + ssize_t bytes; /* Used Buffer length */ + int fd; /* Filedescriptor */ + int res; /* Result from write() */ + + fprintf(stderr, "Export GPIO pin %d\n", pin); + + fd = open("/sys/class/gpio/export", O_WRONLY); + + if (fd < 0) + { + perror("Could not export GPIO pin(open)!\n"); + return (-1); + } + + bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin); + res = write(fd, buffer, bytes); + + if (res < 0) + { + perror("Could not export GPIO pin(write)!\n"); + return (-1); + } + + close(fd); + delay(100); + + return (0); +} + +/* Deactivate GPIO pin + * Write GPIO pin number to /sys/class/gpio/unexport + * Result: 0 = success, -1 = error + */ +int gpio_unexport(int pin) +{ + char buffer[MAX_STRBUF_SIZE]; /* Output Buffer */ + ssize_t bytes; /* Used Buffer length */ + int fd; /* Filedescriptor */ + int res; /* Result from write() */ + + fprintf(stderr, "Unexport GPIO pin %d\n", pin); + + close(gpioFds[pin]); + + fd = open("/sys/class/gpio/unexport", O_WRONLY); + + if (fd < 0) + { + perror("Could not unexport GPIO pin(open)!\n"); + return (-1); + } + + bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin); + res = write(fd, buffer, bytes); + + if (res < 0) + { + perror("Could not unexport GPIO pin(write)!\n"); + return (-1); + } + + close(fd); + return (0); +} + +/* Set GPIO pin mode (input/output) + * Write GPIO pin number to /sys/class/gpioXX/direction + * Direction: 0 = input, 1 = output + * Result: 0 = success, -1 = error + */ +int gpio_direction(int pin, int dir) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int fd; /* Filedescriptor */ + int res; /* Result from write() */ + + fprintf(stderr, "Set GPIO direction for pin %d to %s\n", pin, (dir == INPUT) ? "INPUT" : "OUTPUT"); + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/direction", pin); + fd = open(path, O_WRONLY); + + if (fd < 0) + { + perror("Could not set mode for GPIO pin(open)!\n"); + return (-1); + } + + switch (dir) + { + case INPUT: + res = write(fd, "in", 2); + break; + + case OUTPUT: + res = write(fd, "out", 3); + break; + + default: + res = -1; + break; + } + + if (res < 0) + { + perror("Could not set mode for GPIO pin(write)!\n"); + return (-1); + } + + close(fd); + return (0); +} + +/* Read from GPIO pin + * Result: -1 = error, 0/1 = GPIO pin state + */ +int gpio_read(int pin) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + char c; + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin); + + if (gpioFds[pin] < 0) + gpioFds[pin] = open(path, O_RDWR); + + if (gpioFds[pin] < 0) + { + perror("Could not read from GPIO(open)!\n"); + return (-1); + } + + lseek(gpioFds[pin], 0L, SEEK_SET); + + if (read(gpioFds[pin], &c, 1) < 0) + { + perror("Could not read from GPIO(read)!\n"); + return (-1); + } + + return (c == '0') ? LOW : HIGH; +} + +/* Write to GPIO pin + * Result: -1 = error, 0 = success + */ +int gpio_write(int pin, int value) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int res; /* Result from write()*/ + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin); + + if (gpioFds[pin] < 0) + gpioFds[pin] = open(path, O_RDWR); + + if (gpioFds[pin] < 0) + { + perror("Could not write to GPIO(open)!\n"); + return (-1); + } + + switch (value) + { + case LOW: + res = write(gpioFds[pin], "0\n", 2); + break; + + case HIGH: + res = write(gpioFds[pin], "1\n", 2); + break; + + default: + res = -1; + break; + } + + if (res < 0) + { + perror("Could not write to GPIO(write)!\n"); + return (-1); + } + + return (0); +} + +/* Set GPIO pin edge detection + * 'r' (rising) + * 'f' (falling) + * 'b' (both) + */ +int gpio_edge(unsigned int pin, char edge) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int fd; /* Filedescriptor */ + + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/edge", pin); + + fd = open(path, O_WRONLY | O_NONBLOCK); + + if (fd < 0) + { + perror("Could not set GPIO edge detection(open)!\n"); + return (-1); + } + + switch (edge) + { + case 'r': + strncpy(path, "rising", 8); + break; + + case 'f': + strncpy(path, "falling", 8); + break; + + case 'b': + strncpy(path, "both", 8); + break; + + case 'n': + strncpy(path, "none", 8); + break; + + default: + close(fd); + return (-2); + } + + write(fd, path, strlen(path) + 1); + + close(fd); + return 0; +} + +/* Wait for edge on GPIO pin + * timeout in milliseconds + * Result: <0: error, 0: poll() Timeout, + * 1: edge detected, GPIO pin reads "0" + * 2: edge detected, GPIO pin reads "1" + */ +int gpio_wait(unsigned int pin, int timeout) +{ + char path[MAX_STRBUF_SIZE]; /* Buffer for path */ + int fd; /* Filedescriptor */ + struct pollfd polldat[1]; /* Variable for poll() */ + char buf[MAX_STRBUF_SIZE]; /* Read buffer */ + int rc; /* Result */ + + /* Open GPIO pin */ + snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_RDONLY | O_NONBLOCK); + + if (fd < 0) + { + perror("Could not wait for GPIO edge(open)!\n"); + return (-1); + } + + /* prepare poll() */ + memset((void*)buf, 0, sizeof(buf)); + memset((void*)polldat, 0, sizeof(polldat)); + polldat[0].fd = fd; + polldat[0].events = POLLPRI; + + /* clear any existing detected edges before */ + lseek(fd, 0, SEEK_SET); + rc = read(fd, buf, MAX_STRBUF_SIZE - 1); + + rc = poll(polldat, 1, timeout); + + if (rc < 0) + { + /* poll() failed! */ + perror("Could not wait for GPIO edge(poll)!\n"); + close(fd); + return (-1); + } + + if (rc == 0) + { + /* poll() timeout! */ + close(fd); + return (0); + } + + if (polldat[0].revents & POLLPRI) + { + if (rc < 0) + { + /* read() failed! */ + perror("Could not wait for GPIO edge(read)!\n"); + close(fd); + return (-2); + } + + /* printf("poll() GPIO %d interrupt occurred: %s\n", pin, buf); */ + close(fd); + return (1 + atoi(buf)); + } + + close(fd); + return (-1); +} + +void delayMicrosecondsHard(unsigned int howLong) +{ + struct timeval tNow, tLong, tEnd; + + gettimeofday(&tNow, NULL); + tLong.tv_sec = howLong / 1000000; + tLong.tv_usec = howLong % 1000000; + timeradd(&tNow, &tLong, &tEnd); + + while (timercmp(&tNow, &tEnd, < )) + gettimeofday(&tNow, NULL); +} + +void delayMicroseconds(unsigned int howLong) +{ + struct timespec sleeper; + unsigned int uSecs = howLong % 1000000; + unsigned int wSecs = howLong / 1000000; + + /**/ if (howLong == 0) + return; + else if (howLong < 100) + delayMicrosecondsHard(howLong); + else + { + sleeper.tv_sec = wSecs; + sleeper.tv_nsec = (long)(uSecs * 1000L); + nanosleep(&sleeper, NULL); + } +} + +bool LinuxPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) +{ + struct sockaddr_in address = {0}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(addr); + address.sin_port = htons(port); + + ssize_t retVal = 0; + + do + { + retVal = sendto(_multicastSocketFd, buffer, len, 0, (struct sockaddr*)&address, sizeof(address)); + + if (retVal == -1) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + return false; + } + } while (retVal == -1); + + // printHex("<-", buffer, len); + return true; +} + +void LinuxPlatform::macAddress(uint8_t* mac_address) +{ + memcpy(mac_address, _macAddress, IFHWADDRLEN); +} + +uint32_t LinuxPlatform::currentIpAddress() +{ + return _ipAddress; +} + +uint32_t LinuxPlatform::currentSubnetMask() +{ + return _netmask; +} + +uint32_t LinuxPlatform::currentDefaultGateway() +{ + return _defaultGateway; +} +#endif diff --git a/components/knx/src/linux_platform.h b/components/knx/src/linux_platform.h new file mode 100644 index 0000000..c06ac3d --- /dev/null +++ b/components/knx/src/linux_platform.h @@ -0,0 +1,81 @@ +#pragma once + +#ifdef __linux__ + +#include +#include "knx/platform.h" + +extern int gpio_direction(int pin, int dir); +extern int gpio_read(int pin); +extern int gpio_write(int pin, int value); +extern int gpio_export(int pin); +extern int gpio_unexport(int pin); + +class LinuxPlatform: public Platform +{ + public: + LinuxPlatform(); + virtual ~LinuxPlatform(); + + void cmdLineArgs(int argc, char** argv); + + std::string flashFilePath(); + void flashFilePath(const std::string path); + + // basic stuff + void restart() override; + void fatalError() override; + + // ip config + uint32_t currentIpAddress() override; + uint32_t currentSubnetMask() override; + uint32_t currentDefaultGateway() override; + void macAddress(uint8_t* data) override; + + + //multicast + void setupMultiCast(uint32_t addr, uint16_t port) override; + void closeMultiCast() override; + bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override; + bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; + + //UART + void setupUart() override; + void closeUart() override; + int uartAvailable() override; + size_t writeUart(const uint8_t data) override; + size_t writeUart(const uint8_t* buffer, size_t size) override; + int readUart() override; + size_t readBytesUart(uint8_t* buffer, size_t length) override; + + //spi + void setupSpi() override; + void closeSpi() override; + int readWriteSpi (uint8_t* data, size_t len) override; + + //memory + uint8_t* getEepromBuffer(uint32_t size) override; + void commitToEeprom() override; + void cmdlineArgs(int argc, char** argv); + + private: + uint32_t _multicastAddr = -1; + uint16_t _multicastPort = -1; + int _multicastSocketFd = -1; + + void doMemoryMapping(); + uint8_t* _mappedFile = 0; + int _fd = -1; + int _spiFd = -1; + int _uartFd = -1; + std::string _flashFilePath = "flash.bin"; + char** _args = 0; + + uint8_t _macAddress[6] = {0, 0, 0, 0, 0, 0}; + uint32_t _ipAddress = 0; + uint32_t _netmask = 0; + uint32_t _defaultGateway = 0; +}; + +#endif diff --git a/components/knx/src/rp2040_arduino_platform.cpp b/components/knx/src/rp2040_arduino_platform.cpp new file mode 100644 index 0000000..1145a37 --- /dev/null +++ b/components/knx/src/rp2040_arduino_platform.cpp @@ -0,0 +1,578 @@ +/*----------------------------------------------------- + +Plattform for Raspberry Pi Pico and other RP2040 boards +by SirSydom 2021-2022 + +made to work with arduino-pico - "Raspberry Pi Pico Arduino core, for all RP2040 boards" +by Earl E. Philhower III https://github.com/earlephilhower/arduino-pico + + +RTTI must be set to enabled in the board options + +Uses direct flash reading/writing. +Size ist defined by KNX_FLASH_SIZE (default 4k) - must be a multiple of 4096. +Offset in Flash is defined by KNX_FLASH_OFFSET (default 1,5MiB / 0x180000) - must be a multiple of 4096. + +EEPROM Emulation from arduino-pico core (max 4k) can be use by defining USE_RP2040_EEPROM_EMULATION + +A RAM-buffered Flash can be use by defining USE_RP2040_LARGE_EEPROM_EMULATION + +For usage of KNX-IP you have to define either +- KNX_IP_LAN (use the arduino-pico core's w5500 lwip stack) +- KNX_IP_WIFI (use the arduino-pico core's PiPicoW lwip stack) + +----------------------------------------------------*/ + +#include "rp2040_arduino_platform.h" + +#ifdef ARDUINO_ARCH_RP2040 +#include "knx/bits.h" + +#include + +// Pi Pico specific libs +#include // EEPROM emulation in flash, part of Earl E Philhowers Pi Pico Arduino support +#include // from Pico SDK +#include // from Pico SDK +#include // from Pico SDK + +#ifdef USE_KNX_DMA_UART +#include +// constexpr uint32_t uartDmaTransferCount = 0b1111111111; +constexpr uint32_t uartDmaTransferCount = UINT32_MAX; +constexpr uint8_t uartDmaBufferExp = 8u; // 2**BufferExp +constexpr uint16_t uartDmaBufferSize = (1u << uartDmaBufferExp); +int8_t uartDmaChannel = -1; +volatile uint8_t __attribute__((aligned(uartDmaBufferSize))) uartDmaBuffer[uartDmaBufferSize] = {}; +volatile uint32_t uartDmaReadCount = 0; +volatile uint16_t uartDmaRestartCount = 0; +volatile uint32_t uartDmaWriteCount2 = 0; +volatile uint32_t uartDmaAvail = 0; + +// Returns the number of bytes read since the DMA transfer start +inline uint32_t uartDmaWriteCount() +{ + uartDmaWriteCount2 = uartDmaTransferCount - dma_channel_hw_addr(uartDmaChannel)->transfer_count; + return uartDmaWriteCount2; +} + +// Returns the current write position in the DMA buffer +inline uint16_t uartDmaWriteBufferPosition() +{ + return uartDmaWriteCount() % uartDmaBufferSize; +} + +// Returns the current read position in the DMA buffer +inline uint16_t uartDmaReadBufferPosition() +{ + return uartDmaReadCount % uartDmaBufferSize; +} + +// Returns the current reading position as a pointer +inline uint8_t* uartDmaReadAddr() +{ + return ((uint8_t*)uartDmaBuffer + uartDmaReadBufferPosition()); +} + +// Restarts the transfer after completion. +void __time_critical_func(uartDmaRestart)() +{ + // println("Restart"); + uartDmaRestartCount = uartDmaWriteBufferPosition() - uartDmaReadBufferPosition(); + + // if uartDmaRestartCount == 0, everything has been processed and the read count can be set to 0 again with the restart. + if (uartDmaRestartCount == 0) + { + uartDmaReadCount = 0; + } + + asm volatile("" ::: "memory"); + dma_hw->ints0 = 1u << uartDmaChannel; // clear DMA IRQ0 flag + asm volatile("" ::: "memory"); + dma_channel_set_write_addr(uartDmaChannel, uartDmaBuffer, true); +} +#endif + +#define FLASHPTR ((uint8_t*)XIP_BASE + KNX_FLASH_OFFSET) + +#ifndef USE_RP2040_EEPROM_EMULATION + #if KNX_FLASH_SIZE % 4096 + #error "KNX_FLASH_SIZE must be multiple of 4096" + #endif + + #if KNX_FLASH_OFFSET % 4096 + #error "KNX_FLASH_OFFSET must be multiple of 4096" + #endif +#endif + +#ifdef KNX_IP_LAN + extern Wiznet5500lwIP KNX_NETIF; +#elif defined(KNX_IP_WIFI) +#elif defined(KNX_IP_GENERIC) + +#endif + +RP2040ArduinoPlatform::RP2040ArduinoPlatform() +#if !defined(KNX_NO_DEFAULT_UART) && !defined(USE_KNX_DMA_UART) + : ArduinoPlatform(&KNX_SERIAL) +#endif +{ +#ifdef KNX_UART_RX_PIN + _rxPin = KNX_UART_RX_PIN; +#endif +#ifdef KNX_UART_TX_PIN + _txPin = KNX_UART_TX_PIN; +#endif +#ifndef USE_RP2040_EEPROM_EMULATION + _memoryType = Flash; +#endif +} + +RP2040ArduinoPlatform::RP2040ArduinoPlatform(HardwareSerial* s) + : ArduinoPlatform(s) +{ +#ifndef USE_RP2040_EEPROM_EMULATION + _memoryType = Flash; +#endif +} + +void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) +{ + _rxPin = rxPin; + _txPin = txPin; +} + +bool RP2040ArduinoPlatform::overflowUart() +{ +#ifdef USE_KNX_DMA_UART + // during dma restart + bool ret; + const uint32_t writeCount = uartDmaWriteCount(); + + if (uartDmaRestartCount > 0) + ret = writeCount >= (uartDmaBufferSize - uartDmaRestartCount - 1); + else + ret = (writeCount - uartDmaReadCount) > uartDmaBufferSize; + + // if (ret) + // { + // println(uartDmaWriteBufferPosition()); + // println(uartDmaReadBufferPosition()); + // println(uartDmaWriteCount()); + // println(uartDmaReadCount); + // println(uartDmaRestartCount); + // printHex("BUF: ", (const uint8_t *)uartDmaBuffer, uartDmaBufferSize); + // println("OVERFLOW"); + // while (true) + // ; + // } + return ret; +#else + SerialUART* serial = dynamic_cast(_knxSerial); + return serial->overflow(); +#endif +} + +void RP2040ArduinoPlatform::setupUart() +{ +#ifdef USE_KNX_DMA_UART + + if (uartDmaChannel == -1) + { + // configure uart0 + gpio_set_function(_rxPin, GPIO_FUNC_UART); + gpio_set_function(_txPin, GPIO_FUNC_UART); + uart_init(KNX_DMA_UART, 19200); + uart_set_hw_flow(KNX_DMA_UART, false, false); + uart_set_format(KNX_DMA_UART, 8, 1, UART_PARITY_EVEN); + uart_set_fifo_enabled(KNX_DMA_UART, false); + + // configure uart0 + uartDmaChannel = dma_claim_unused_channel(true); // get free channel for dma + dma_channel_config dmaConfig = dma_channel_get_default_config(uartDmaChannel); + channel_config_set_transfer_data_size(&dmaConfig, DMA_SIZE_8); + channel_config_set_read_increment(&dmaConfig, false); + channel_config_set_write_increment(&dmaConfig, true); + channel_config_set_high_priority(&dmaConfig, true); + channel_config_set_ring(&dmaConfig, true, uartDmaBufferExp); + channel_config_set_dreq(&dmaConfig, KNX_DMA_UART_DREQ); + dma_channel_set_read_addr(uartDmaChannel, &uart_get_hw(uart0)->dr, false); + dma_channel_set_write_addr(uartDmaChannel, uartDmaBuffer, false); + dma_channel_set_trans_count(uartDmaChannel, uartDmaTransferCount, false); + dma_channel_set_config(uartDmaChannel, &dmaConfig, true); + dma_channel_set_irq1_enabled(uartDmaChannel, true); + // irq_add_shared_handler(KNX_DMA_IRQ, uartDmaRestart, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); + irq_set_exclusive_handler(KNX_DMA_IRQ, uartDmaRestart); + irq_set_enabled(KNX_DMA_IRQ, true); + } + +#else + SerialUART* serial = dynamic_cast(_knxSerial); + + if (serial) + { + if (_rxPin != UART_PIN_NOT_DEFINED) + serial->setRX(_rxPin); + + if (_txPin != UART_PIN_NOT_DEFINED) + serial->setTX(_txPin); + + serial->setPollingMode(); + serial->setFIFOSize(64); + } + + _knxSerial->begin(19200, SERIAL_8E1); + + while (!_knxSerial) + ; + +#endif +} + +#ifdef USE_KNX_DMA_UART +int RP2040ArduinoPlatform::uartAvailable() +{ + if (uartDmaChannel == -1) + return 0; + + if (uartDmaRestartCount > 0) + { + return uartDmaRestartCount; + } + else + { + uint32_t tc = dma_channel_hw_addr(uartDmaChannel)->transfer_count; + uartDmaAvail = tc; + int test = uartDmaTransferCount - tc - uartDmaReadCount; + return test; + } +} + +int RP2040ArduinoPlatform::readUart() +{ + if (!uartAvailable()) + return -1; + + int ret = uartDmaReadAddr()[0]; + // print("< "); + // println(ret, HEX); + uartDmaReadCount++; + + if (uartDmaRestartCount > 0) + { + // process previouse buffer + uartDmaRestartCount--; + + // last char, then reset read count to start at new writer position + if (uartDmaRestartCount == 0) + uartDmaReadCount = 0; + } + + return ret; +} + +size_t RP2040ArduinoPlatform::writeUart(const uint8_t data) +{ + if (uartDmaChannel == -1) + return 0; + + // print("> "); + // println(data, HEX); + while (!uart_is_writable(uart0)) + ; + + uart_putc_raw(uart0, data); + return 1; +} + +void RP2040ArduinoPlatform::closeUart() +{ + if (uartDmaChannel >= 0) + { + dma_channel_cleanup(uartDmaChannel); + irq_set_enabled(DMA_IRQ_0, false); + uart_deinit(uart0); + uartDmaChannel = -1; + uartDmaReadCount = 0; + uartDmaRestartCount = 0; + } +} +#endif + +uint32_t RP2040ArduinoPlatform::uniqueSerialNumber() +{ + pico_unique_board_id_t id; // 64Bit unique serial number from the QSPI flash + + noInterrupts(); + rp2040.idleOtherCore(); + + flash_get_unique_id(id.id); // pico_get_unique_board_id(&id); + + rp2040.resumeOtherCore(); + interrupts(); + + // use lower 4 byte and convert to unit32_t + uint32_t uid = ((uint32_t)(id.id[4]) << 24) | ((uint32_t)(id.id[5]) << 16) | ((uint32_t)(id.id[6]) << 8) | (uint32_t)(id.id[7]); + + return uid; +} + +void RP2040ArduinoPlatform::restart() +{ + println("restart"); + watchdog_reboot(0, 0, 0); +} + +#ifdef USE_RP2040_EEPROM_EMULATION + +#pragma warning "Using EEPROM Simulation" + +#ifdef USE_RP2040_LARGE_EEPROM_EMULATION + +uint8_t* RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) +{ + if (size % 4096) + { + println("KNX_FLASH_SIZE must be a multiple of 4096"); + fatalError(); + } + + if (!_rambuff_initialized) + { + memcpy(_rambuff, FLASHPTR, KNX_FLASH_SIZE); + _rambuff_initialized = true; + } + + return _rambuff; +} + +void RP2040ArduinoPlatform::commitToEeprom() +{ + noInterrupts(); + rp2040.idleOtherCore(); + + // ToDo: write block-by-block to prevent writing of untouched blocks + if (memcmp(_rambuff, FLASHPTR, KNX_FLASH_SIZE)) + { + flash_range_erase(KNX_FLASH_OFFSET, KNX_FLASH_SIZE); + flash_range_program(KNX_FLASH_OFFSET, _rambuff, KNX_FLASH_SIZE); + } + + rp2040.resumeOtherCore(); + interrupts(); +} + +#else + +uint8_t* RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) +{ + if (size > 4096) + { + println("KNX_FLASH_SIZE to big for EEPROM emulation (max. 4kB)"); + fatalError(); + } + + uint8_t* eepromptr = EEPROM.getDataPtr(); + + if (eepromptr == nullptr) + { + EEPROM.begin(4096); + eepromptr = EEPROM.getDataPtr(); + } + + return eepromptr; +} + +void RP2040ArduinoPlatform::commitToEeprom() +{ + EEPROM.commit(); +} + +#endif + +#else + +size_t RP2040ArduinoPlatform::flashEraseBlockSize() +{ + return 16; // 16 pages x 256byte/page = 4096byte +} + +size_t RP2040ArduinoPlatform::flashPageSize() +{ + return 256; +} + +uint8_t* RP2040ArduinoPlatform::userFlashStart() +{ + return (uint8_t*)XIP_BASE + KNX_FLASH_OFFSET; +} + +size_t RP2040ArduinoPlatform::userFlashSizeEraseBlocks() +{ + if (KNX_FLASH_SIZE <= 0) + return 0; + else + return ((KNX_FLASH_SIZE - 1) / (flashPageSize() * flashEraseBlockSize())) + 1; +} + +void RP2040ArduinoPlatform::flashErase(uint16_t eraseBlockNum) +{ + noInterrupts(); + rp2040.idleOtherCore(); + + flash_range_erase(KNX_FLASH_OFFSET + eraseBlockNum * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + + rp2040.resumeOtherCore(); + interrupts(); +} + +void RP2040ArduinoPlatform::flashWritePage(uint16_t pageNumber, uint8_t* data) +{ + noInterrupts(); + rp2040.idleOtherCore(); + + flash_range_program(KNX_FLASH_OFFSET + pageNumber * flashPageSize(), data, flashPageSize()); + + rp2040.resumeOtherCore(); + interrupts(); +} + +void RP2040ArduinoPlatform::writeBufferedEraseBlock() +{ + if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) + { + noInterrupts(); + rp2040.idleOtherCore(); + + flash_range_erase(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + flash_range_program(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), _eraseblockBuffer, flashPageSize() * flashEraseBlockSize()); + + rp2040.resumeOtherCore(); + interrupts(); + + _bufferedEraseblockDirty = false; + } +} +#endif + +#if defined(KNX_NETIF) +uint32_t RP2040ArduinoPlatform::currentIpAddress() +{ + return KNX_NETIF.localIP(); +} +uint32_t RP2040ArduinoPlatform::currentSubnetMask() +{ + return KNX_NETIF.subnetMask(); +} +uint32_t RP2040ArduinoPlatform::currentDefaultGateway() +{ + return KNX_NETIF.gatewayIP(); +} +void RP2040ArduinoPlatform::macAddress(uint8_t* addr) +{ + KNX_NETIF.macAddress(addr); +} + +// multicast +void RP2040ArduinoPlatform::setupMultiCast(uint32_t addr, uint16_t port) +{ + mcastaddr = IPAddress(htonl(addr)); + _port = port; + uint8_t result = _udp.beginMulticast(mcastaddr, port); + (void)result; + +#ifdef KNX_IP_GENERIC + // if(!_unicast_socket_setup) + // _unicast_socket_setup = UDP_UNICAST.begin(3671); +#endif + + // print("Setup Mcast addr: "); + // print(mcastaddr.toString().c_str()); + // print(" on port: "); + // print(port); + // print(" result "); + // println(result); +} + +void RP2040ArduinoPlatform::closeMultiCast() +{ + _udp.stop(); +} + +bool RP2040ArduinoPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) +{ + // printHex("<- ",buffer, len); + + // ToDo: check if Ethernet is able to receive, return false if not + _udp.beginPacket(mcastaddr, _port); + _udp.write(buffer, len); + _udp.endPacket(); + return true; +} + +int RP2040ArduinoPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) +{ + int len = _udp.parsePacket(); + + if (len == 0) + return 0; + + if (len > maxLen) + { + println("Unexpected UDP data packet length - drop packet"); + + for (size_t i = 0; i < len; i++) + _udp.read(); + + return 0; + } + + _udp.read(buffer, len); + _remoteIP = _udp.remoteIP(); + _remotePort = _udp.remotePort(); + src_addr = htonl(_remoteIP); + src_port = _remotePort; + + // print("Remote IP: "); + // print(_udp.remoteIP().toString().c_str()); + // printHex("-> ", buffer, len); + + return len; +} + +// unicast +bool RP2040ArduinoPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) +{ + IPAddress ucastaddr(htonl(addr)); + + if (!addr) + ucastaddr = _remoteIP; + + if (!port) + port = _remotePort; + + // print("sendBytesUniCast to:"); + // println(ucastaddr.toString().c_str()); + +#ifdef KNX_IP_GENERIC + + if (!_unicast_socket_setup) + _unicast_socket_setup = UDP_UNICAST.begin(3671); + +#endif + + if (UDP_UNICAST.beginPacket(ucastaddr, port) == 1) + { + UDP_UNICAST.write(buffer, len); + + if (UDP_UNICAST.endPacket() == 0) + println("sendBytesUniCast endPacket fail"); + } + else + println("sendBytesUniCast beginPacket fail"); + + return true; +} +#endif + +#endif diff --git a/components/knx/src/rp2040_arduino_platform.h b/components/knx/src/rp2040_arduino_platform.h new file mode 100644 index 0000000..735fcd0 --- /dev/null +++ b/components/knx/src/rp2040_arduino_platform.h @@ -0,0 +1,157 @@ +#ifdef ARDUINO +#pragma once + +#include "arduino_platform.h" + +#include "Arduino.h" + +#ifdef ARDUINO_ARCH_RP2040 + +#ifndef USE_RP2040_EEPROM_EMULATION + #ifndef KNX_FLASH_OFFSET + #define KNX_FLASH_OFFSET 0x180000 // 1.5MiB + #pragma warning "KNX_FLASH_OFFSET not defined, using 0x180000" + #endif +#endif + +#ifdef USE_RP2040_LARGE_EEPROM_EMULATION + #define USE_RP2040_EEPROM_EMULATION +#endif + +#ifndef KNX_SERIAL + #pragma warn "KNX_SERIAL not defined, using Serial1" + #define KNX_SERIAL Serial1 +#endif + +#ifdef KNX_IP_LAN + #if ARDUINO_PICO_MAJOR * 10000 + ARDUINO_PICO_MINOR * 100 + ARDUINO_PICO_REVISION < 30700 + #pragma error "arduino-pico >= 3.7.0 needed" + #endif + #define KNX_NETIF Eth + + #include "SPI.h" + #include + +#else + #include + #define KNX_NETIF WiFi +#endif + +#if USE_KNX_DMA_UART == 1 + #define KNX_DMA_UART uart1 + #define KNX_DMA_UART_IRQ UART1_IRQ + #define KNX_DMA_UART_DREQ DREQ_UART1_RX +#else + #define KNX_DMA_UART uart0 + #define KNX_DMA_UART_IRQ UART0_IRQ + #define KNX_DMA_UART_DREQ DREQ_UART0_RX +#endif + +#if USE_KNX_DMA_IRQ == 1 + #define KNX_DMA_IRQ DMA_IRQ_1 +#else + #define KNX_DMA_IRQ DMA_IRQ_0 +#endif + + +class RP2040ArduinoPlatform : public ArduinoPlatform +{ + public: + RP2040ArduinoPlatform(); + RP2040ArduinoPlatform( HardwareSerial* s); + + // uart + void knxUartPins(pin_size_t rxPin, pin_size_t txPin); + void setupUart() override; + bool overflowUart() override; +#ifdef USE_KNX_DMA_UART + int uartAvailable() override; + void closeUart() override; + void knxUart( HardwareSerial* serial) override {}; + HardwareSerial* knxUart() override + { + return nullptr; + }; + size_t writeUart(const uint8_t data) override; + size_t writeUart(const uint8_t* buffer, size_t size) override + { + return 0; + }; + int readUart() override; + size_t readBytesUart(uint8_t* buffer, size_t length) override + { + return 0; + }; + void flushUart() override {}; +#endif + + + // unique serial number + uint32_t uniqueSerialNumber() override; + + void restart(); + +#ifdef USE_RP2040_EEPROM_EMULATION + uint8_t* getEepromBuffer(uint32_t size); + void commitToEeprom(); + +#ifdef USE_RP2040_LARGE_EEPROM_EMULATION + uint8_t _rambuff[KNX_FLASH_SIZE]; + bool _rambuff_initialized = false; +#endif +#else + + // size of one EraseBlock in pages + virtual size_t flashEraseBlockSize(); + // size of one flash page in bytes + virtual size_t flashPageSize(); + // start of user flash aligned to start of an erase block + virtual uint8_t* userFlashStart(); + // size of the user flash in EraseBlocks + virtual size_t userFlashSizeEraseBlocks(); + //relativ to userFlashStart + virtual void flashErase(uint16_t eraseBlockNum); + //write a single page to flash (pageNumber relative to userFashStart + virtual void flashWritePage(uint16_t pageNumber, uint8_t* data); + + // writes _eraseblockBuffer to flash - overrides Plattform::writeBufferedEraseBlock() for performance optimization only + void writeBufferedEraseBlock(); +#endif + + +#if defined(KNX_NETIF) + uint32_t currentIpAddress() override; + uint32_t currentSubnetMask() override; + uint32_t currentDefaultGateway() override; + void macAddress(uint8_t* addr) override; + + // multicast + void setupMultiCast(uint32_t addr, uint16_t port) override; + void closeMultiCast() override; + bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) override; + + // unicast + bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; + +#define UDP_UNICAST _udp + protected: + WiFiUDP _udp; + protected: + IPAddress mcastaddr; + protected: + uint16_t _port; +#endif + protected: + pin_size_t _rxPin = UART_PIN_NOT_DEFINED; + protected: + pin_size_t _txPin = UART_PIN_NOT_DEFINED; + + protected: + IPAddress _remoteIP = 0; + protected: + uint16_t _remotePort = 0; +}; + +#endif +#endif // ARDUINO \ No newline at end of file diff --git a/components/knx/src/samd_platform.cpp b/components/knx/src/samd_platform.cpp new file mode 100644 index 0000000..ce26b84 --- /dev/null +++ b/components/knx/src/samd_platform.cpp @@ -0,0 +1,242 @@ +#include "samd_platform.h" + +#ifdef ARDUINO_ARCH_SAMD +#include + +#include +#ifdef USE_SAMD_EEPROM_EMULATION + #include +#endif + +#if KNX_FLASH_SIZE % 1024 + #error "KNX_FLASH_SIZE must be multiple of 1024" +#endif + +#ifndef KNX_SERIAL + #define KNX_SERIAL Serial1 +#endif + +SamdPlatform::SamdPlatform() +#ifndef KNX_NO_DEFAULT_UART + : ArduinoPlatform(&KNX_SERIAL) +#endif +{ +#ifndef USE_SAMD_EEPROM_EMULATION + init(); +#endif +} + +SamdPlatform::SamdPlatform( HardwareSerial* s) : ArduinoPlatform(s) +{ +#ifndef USE_SAMD_EEPROM_EMULATION + init(); +#endif +} + +uint32_t SamdPlatform::uniqueSerialNumber() +{ +#if defined (__SAMD51__) + // SAMD51 from section 9.6 of the datasheet +#define SERIAL_NUMBER_WORD_0 *(volatile uint32_t*)(0x008061FC) +#define SERIAL_NUMBER_WORD_1 *(volatile uint32_t*)(0x00806010) +#define SERIAL_NUMBER_WORD_2 *(volatile uint32_t*)(0x00806014) +#define SERIAL_NUMBER_WORD_3 *(volatile uint32_t*)(0x00806018) +#else + //#elif defined (__SAMD21E17A__) || defined(__SAMD21G18A__) || defined(__SAMD21E18A__) || defined(__SAMD21J18A__) + // SAMD21 from section 9.3.3 of the datasheet +#define SERIAL_NUMBER_WORD_0 *(volatile uint32_t*)(0x0080A00C) +#define SERIAL_NUMBER_WORD_1 *(volatile uint32_t*)(0x0080A040) +#define SERIAL_NUMBER_WORD_2 *(volatile uint32_t*)(0x0080A044) +#define SERIAL_NUMBER_WORD_3 *(volatile uint32_t*)(0x0080A048) +#endif + + return SERIAL_NUMBER_WORD_0 ^ SERIAL_NUMBER_WORD_1 ^ SERIAL_NUMBER_WORD_2 ^ SERIAL_NUMBER_WORD_3; +} + +void SamdPlatform::restart() +{ + println("restart"); + NVIC_SystemReset(); +} + +#ifdef USE_SAMD_EEPROM_EMULATION +#pragma warning "Using EEPROM Simulation" +uint8_t* SamdPlatform::getEepromBuffer(uint32_t size) +{ + //EEPROM.begin(size); + if (size > EEPROM_EMULATION_SIZE) + fatalError(); + + return EEPROM.getDataPtr(); +} + +void SamdPlatform::commitToEeprom() +{ + EEPROM.commit(); +} +#else + +extern uint32_t __etext; +extern uint32_t __data_start__; +extern uint32_t __data_end__; + +static const uint32_t pageSizes[] = {8, 16, 32, 64, 128, 256, 512, 1024}; + +void SamdPlatform::init() +{ + _memoryType = Flash; + _pageSize = pageSizes[NVMCTRL->PARAM.bit.PSZ]; + _pageCnt = NVMCTRL->PARAM.bit.NVMP; + _rowSize = PAGES_PER_ROW * _pageSize; + + // find end of program flash and set limit to next row + uint32_t endEddr = (uint32_t)(&__etext + (&__data_end__ - &__data_start__)); // text + data MemoryBlock +#ifdef KNX_FLASH_OFFSET + _MemoryStart = KNX_FLASH_OFFSET; + _MemoryEnd = KNX_FLASH_OFFSET + KNX_FLASH_SIZE; +#else + _MemoryStart = getRowAddr(_pageSize * _pageCnt - KNX_FLASH_SIZE - 1); // 23295 + _MemoryEnd = getRowAddr(_pageSize * _pageCnt - 1); +#endif + + // chosen flash size is not available anymore + if (_MemoryStart < endEddr) + { + println("KNX_FLASH_SIZE is not available (possible too much flash use by firmware)"); + fatalError(); + } +} + +size_t SamdPlatform::flashEraseBlockSize() +{ + return PAGES_PER_ROW; +} + +size_t SamdPlatform::flashPageSize() +{ + return _pageSize; +} + +uint8_t* SamdPlatform::userFlashStart() +{ + return (uint8_t*)_MemoryStart; +} + +size_t SamdPlatform::userFlashSizeEraseBlocks() +{ + if (KNX_FLASH_SIZE <= 0) + return 0; + else + return ((KNX_FLASH_SIZE - 1) / (flashPageSize() * flashEraseBlockSize())) + 1; +} + +void SamdPlatform::flashErase(uint16_t eraseBlockNum) +{ + noInterrupts(); + + eraseRow((void*)(_MemoryStart + eraseBlockNum * _rowSize)); + // flash_range_erase(KNX_FLASH_OFFSET + eraseBlockNum * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + + interrupts(); +} + +void SamdPlatform::flashWritePage(uint16_t pageNumber, uint8_t* data) +{ + noInterrupts(); + + write((void*)(_MemoryStart + pageNumber * _pageSize), data, _pageSize); + // flash_range_program(KNX_FLASH_OFFSET + pageNumber * flashPageSize(), data, flashPageSize()); + + interrupts(); +} + +void SamdPlatform::writeBufferedEraseBlock() +{ + if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) + { + noInterrupts(); + + eraseRow((void*)(_MemoryStart + _bufferedEraseblockNumber * _rowSize)); + write((void*)(_MemoryStart + _bufferedEraseblockNumber * _rowSize), _eraseblockBuffer, _rowSize); + // flash_range_erase(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + // flash_range_program(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), _eraseblockBuffer, flashPageSize() * flashEraseBlockSize()); + + interrupts(); + + _bufferedEraseblockDirty = false; + } +} + +uint32_t SamdPlatform::getRowAddr(uint32_t flasAddr) +{ + return flasAddr & ~(_rowSize - 1); +} + +void SamdPlatform::write(const volatile void* flash_ptr, const void* data, uint32_t size) +{ + // Calculate data boundaries + size = (size + 3) / 4; + volatile uint32_t* src_addr = (volatile uint32_t*)data; + volatile uint32_t* dst_addr = (volatile uint32_t*)flash_ptr; + // volatile uint32_t *dst_addr = (volatile uint32_t *)flash_ptr; + // const uint8_t *src_addr = (uint8_t *)data; + + // Disable automatic page write + NVMCTRL->CTRLB.bit.MANW = 1; + + // Do writes in pages + while (size) + { + // Execute "PBC" Page Buffer Clear + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC; + + while (NVMCTRL->INTFLAG.bit.READY == 0) + { + } + + // Fill page buffer + uint32_t i; + + for (i = 0; i < (_pageSize / 4) && size; i++) + { + *dst_addr = *src_addr; + src_addr++; + dst_addr++; + size--; + } + + // Execute "WP" Write Page + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP; + + while (NVMCTRL->INTFLAG.bit.READY == 0) + { + } + } +} + +void SamdPlatform::erase(const volatile void* flash_ptr, uint32_t size) +{ + const uint8_t* ptr = (const uint8_t*)flash_ptr; + + while (size > _rowSize) + { + eraseRow(ptr); + ptr += _rowSize; + size -= _rowSize; + } + + eraseRow(ptr); +} + +void SamdPlatform::eraseRow(const volatile void* flash_ptr) +{ + NVMCTRL->ADDR.reg = ((uint32_t)flash_ptr) / 2; + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER; + + while (!NVMCTRL->INTFLAG.bit.READY) + { + } +} + +#endif +#endif diff --git a/components/knx/src/samd_platform.h b/components/knx/src/samd_platform.h new file mode 100644 index 0000000..6ac9a51 --- /dev/null +++ b/components/knx/src/samd_platform.h @@ -0,0 +1,57 @@ +#ifdef ARDUINO +#include "arduino_platform.h" + +#include "Arduino.h" + +#ifdef ARDUINO_ARCH_SAMD + +#define PAGES_PER_ROW 4 + +class SamdPlatform : public ArduinoPlatform +{ + public: + SamdPlatform(); + SamdPlatform( HardwareSerial* s); + + // unique serial number + uint32_t uniqueSerialNumber() override; + + void restart(); +#ifdef USE_SAMD_EEPROM_EMULATION + uint8_t* getEepromBuffer(uint32_t size); + void commitToEeprom(); +#else + // size of one EraseBlock in pages + virtual size_t flashEraseBlockSize(); + // size of one flash page in bytes + virtual size_t flashPageSize(); + // start of user flash aligned to start of an erase block + virtual uint8_t* userFlashStart(); + // size of the user flash in EraseBlocks + virtual size_t userFlashSizeEraseBlocks(); + // relativ to userFlashStart + virtual void flashErase(uint16_t eraseBlockNum); + // write a single page to flash (pageNumber relative to userFashStart + virtual void flashWritePage(uint16_t pageNumber, uint8_t* data); + + // writes _eraseblockBuffer to flash - overrides Plattform::writeBufferedEraseBlock() for performance optimization only + void writeBufferedEraseBlock(); + + private: + void init(); + uint32_t _MemoryEnd = 0; + uint32_t _MemoryStart = 0; + uint32_t _pageSize; + uint32_t _rowSize; + uint32_t _pageCnt; + + uint32_t getRowAddr(uint32_t flasAddr); + void write(const volatile void* flash_ptr, const void* data, uint32_t size); + void erase(const volatile void* flash_ptr, uint32_t size); + void eraseRow(const volatile void* flash_ptr); + +#endif +}; + +#endif +#endif // ARDUINO \ No newline at end of file diff --git a/components/knx/src/stm32_platform.cpp b/components/knx/src/stm32_platform.cpp new file mode 100644 index 0000000..6e55ea4 --- /dev/null +++ b/components/knx/src/stm32_platform.cpp @@ -0,0 +1,74 @@ +#include "stm32_platform.h" + +#ifdef ARDUINO_ARCH_STM32 +#include +#include "knx/bits.h" + +#ifndef KNX_SERIAL + #define KNX_SERIAL Serial2 +#endif + +Stm32Platform::Stm32Platform() +#ifndef KNX_NO_DEFAULT_UART + : ArduinoPlatform(&KNX_SERIAL) +#endif +{ +} + +Stm32Platform::Stm32Platform( HardwareSerial* s) : ArduinoPlatform(s) +{ +} + +Stm32Platform::~Stm32Platform() +{ + delete [] _eepromPtr; +} + +uint32_t Stm32Platform::uniqueSerialNumber() +{ + return HAL_GetUIDw0() ^ HAL_GetUIDw1() ^ HAL_GetUIDw2(); +} + +void Stm32Platform::restart() +{ + NVIC_SystemReset(); +} + +uint8_t* Stm32Platform::getEepromBuffer(uint32_t size) +{ + // check if the buffer already exists + if (_eepromPtr == nullptr) // we need to initialize the buffer first + { + if (size > E2END + 1) + { + fatalError(); + } + + _eepromSize = size; + _eepromPtr = new uint8_t[size]; + eeprom_buffer_fill(); + + for (uint16_t i = 0; i < size; ++i) + _eepromPtr[i] = eeprom_buffered_read_byte(i); + } + + return _eepromPtr; +} + +void Stm32Platform::commitToEeprom() +{ + if (_eepromPtr == nullptr || _eepromSize == 0) + return; + + for (uint16_t i = 0; i < _eepromSize; ++i) + eeprom_buffered_write_byte(i, _eepromPtr[i]); + + // For some GD32 chips, the flash needs to be unlocked twice + // and the first call will fail. If the first call is + // successful, the second one (inside eeprom_buffer_flush) + // does nothing. + HAL_FLASH_Unlock(); + eeprom_buffer_flush(); +} + +#endif diff --git a/components/knx/src/stm32_platform.h b/components/knx/src/stm32_platform.h new file mode 100644 index 0000000..daa50a0 --- /dev/null +++ b/components/knx/src/stm32_platform.h @@ -0,0 +1,25 @@ +#ifdef ARDUINO_ARCH_STM32 +#include "arduino_platform.h" + +class Stm32Platform : public ArduinoPlatform +{ + public: + Stm32Platform(); + Stm32Platform( HardwareSerial* s); + ~Stm32Platform(); + + // unique serial number + uint32_t uniqueSerialNumber() override; + + // basic stuff + void restart(); + + //memory + uint8_t* getEepromBuffer(uint32_t size); + void commitToEeprom(); + private: + uint8_t* _eepromPtr = nullptr; + uint16_t _eepromSize = 0; +}; + +#endif diff --git a/dependencies.lock b/dependencies.lock index a7224b1..af85dd8 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -1,164 +1,63 @@ dependencies: espressif/button: - component_hash: - fccb18c37f1cfe0797b74a53a44d3f400f5fd01f4993b40052dfb7f401915089 - dependencies: - - name: espressif/cmake_utilities - registry_url: https://components.espressif.com - require: private - version: '*' - - name: idf - require: private - version: '>=4.0' + dependencies: [] source: - registry_url: https://components.espressif.com - type: service + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__button + type: local version: 4.1.5 espressif/cmake_utilities: component_hash: - 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f + 05165f30922b422b4b90c08845e6d449329b97370fbd06309803d8cb539d79e3 dependencies: - name: idf require: private version: '>=4.1' source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service - version: 0.5.3 + version: 1.1.1 espressif/esp_lcd_touch: - component_hash: - 3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862 - dependencies: - - name: idf - require: private - version: '>=4.4.2' + dependencies: [] source: - registry_url: https://components.espressif.com - type: service + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__esp_lcd_touch + type: local version: 1.2.1 espressif/esp_lcd_touch_gt911: - component_hash: - be02e243d18b9a661bc13b0d22c0a5cfa3f708cf04d6eb059772276c8c8a4d76 - dependencies: - - name: espressif/esp_lcd_touch - registry_url: https://components.espressif.com - require: public - version: ^1.2.0 - - name: idf - require: private - version: '>=4.4.2' + dependencies: [] source: - registry_url: https://components.espressif.com/ - type: service + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__esp_lcd_touch_gt911 + type: local version: 1.2.0~1 espressif/esp_lv_decoder: - component_hash: - 0eb7b2bceaf73484ef80f5004337ee31617b2450a3d40621812998a47e7dd349 - dependencies: - - name: espressif/esp_new_jpeg - registry_url: https://components.espressif.com - require: private - version: 0.* - - name: espressif/libpng - registry_url: https://components.espressif.com - require: private - version: 1.* - - name: idf - require: private - version: '>=5.3' - - name: lvgl/lvgl - registry_url: https://components.espressif.com - require: private - version: '>=8,<10' + dependencies: [] source: - registry_url: https://components.espressif.com - type: service - targets: - - esp32 - - esp32s2 - - esp32s3 - - esp32p4 - - esp32c2 - - esp32c3 - - esp32c5 - - esp32c6 + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__esp_lv_decoder + type: local version: 0.3.2 espressif/esp_lv_fs: - component_hash: - 66896007884b817df34c964f9a114fff538ee2674e99fee7159162498b93f94b - dependencies: - - name: espressif/cmake_utilities - registry_url: https://components.espressif.com - require: private - version: 0.* - - name: espressif/esp_mmap_assets - registry_url: https://components.espressif.com - require: private - version: '>=1.2' - - name: idf - require: private - version: '>=4.4' - - name: lvgl/lvgl - registry_url: https://components.espressif.com - require: private - version: '>=8,<10' + dependencies: [] source: - registry_url: https://components.espressif.com - type: service + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__esp_lv_fs + type: local version: 1.0.1 espressif/esp_lvgl_adapter: - component_hash: - 4ba6ad754b2533cb582bff81ba672ea7a682f4724a02327e57f96e9c73d330c2 - dependencies: - - name: espressif/button - registry_url: https://components.espressif.com - require: public - version: 4.* - - name: espressif/esp_lcd_touch - registry_url: https://components.espressif.com - require: public - version: 1.* - - name: espressif/esp_lv_decoder - registry_url: https://components.espressif.com - require: public - version: 0.* - - name: espressif/esp_lv_fs - registry_url: https://components.espressif.com - require: public - version: 1.* - - name: espressif/freetype - registry_url: https://components.espressif.com - require: public - version: 2.* - - name: espressif/knob - registry_url: https://components.espressif.com - require: public - version: 1.* - - name: idf - require: private - version: '>=5.5' - - name: lvgl/lvgl - registry_url: https://components.espressif.com - require: private - version: '>=8,<10' + dependencies: [] source: - registry_url: https://components.espressif.com/ - type: service + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__esp_lvgl_adapter + type: local version: 0.3.0 espressif/esp_lvgl_port: - component_hash: - f872401524cb645ee6ff1c9242d44fb4ddcfd4d37d7be8b9ed3f4e85a404efcd - dependencies: - - name: idf - require: private - version: '>=5.1' - - name: lvgl/lvgl - registry_url: https://components.espressif.com - require: public - version: '>=8,<10' + dependencies: [] source: - registry_url: https://components.espressif.com/ - type: service + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__esp_lvgl_port + type: local version: 2.7.0 espressif/esp_mmap_assets: component_hash: @@ -172,7 +71,7 @@ dependencies: require: private version: '>=5.0' source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service version: 1.4.0 espressif/esp_new_jpeg: @@ -180,7 +79,7 @@ dependencies: e6af208a875abd0ecfc0213d3751a11b504b463ebde6930f24096047925fa5c1 dependencies: [] source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service targets: - esp32 @@ -200,23 +99,15 @@ dependencies: require: private version: '>=4.4' source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service version: 2.13.3~1 espressif/i2c_bus: - component_hash: - 4e990dc11734316186b489b362c61d41f23f79d58bc169795cec215e528cba14 - dependencies: - - name: espressif/cmake_utilities - registry_url: https://components.espressif.com - require: private - version: '*' - - name: idf - require: private - version: '>=4.0' + dependencies: [] source: - registry_url: https://components.espressif.com - type: service + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/espressif__i2c_bus + type: local version: 1.5.0 espressif/knob: component_hash: @@ -230,7 +121,7 @@ dependencies: require: private version: '>=4.4.1' source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service version: 1.0.2 espressif/libpng: @@ -245,7 +136,7 @@ dependencies: require: private version: ^1.2.13 source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service version: 1.6.52 espressif/zlib: @@ -256,7 +147,7 @@ dependencies: require: private version: '>=4.4' source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service version: 1.3.1 idf: @@ -272,33 +163,31 @@ dependencies: type: service version: 9.4.0 waveshare/esp_lcd_jd9365_10_1: - component_hash: - 6c1336b93a37df2b5be42c49c4c364d0bacdbf96f053a934f881349457fac679 - dependencies: - - name: espressif/cmake_utilities - registry_url: https://components.espressif.com - require: private - version: 0.* - - name: espressif/i2c_bus - registry_url: https://components.espressif.com - require: private - version: ^1.3.0 - - name: idf - require: private - version: '>=5.3' + dependencies: [] source: - registry_url: https://components.espressif.com/ - type: service - targets: - - esp32p4 + path: + /home/thomas/projekte/test1/knxdisplay/managed_components/waveshare__esp_lcd_jd9365_10_1 + type: local version: 1.0.4 direct_dependencies: +- espressif/button +- espressif/cmake_utilities +- espressif/esp_lcd_touch - espressif/esp_lcd_touch_gt911 +- espressif/esp_lv_decoder +- espressif/esp_lv_fs - espressif/esp_lvgl_adapter - espressif/esp_lvgl_port +- espressif/esp_mmap_assets +- espressif/esp_new_jpeg +- espressif/freetype +- espressif/i2c_bus +- espressif/knob +- espressif/libpng +- espressif/zlib - idf - lvgl/lvgl - waveshare/esp_lcd_jd9365_10_1 -manifest_hash: df48f0c889a77855e4d9de37a676a02669553fe2b7fdf43547783bcf8f97c123 +manifest_hash: e5b7ffaadec3a429c21a1bb64eb300dde72478c28abbc4525a195be0ed7a6428 target: esp32p4 version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ed7036d..6ac5b22 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "hello_world_main.c" +idf_component_register(SRCS "KnxWorker.cpp" "Nvs.cpp" "main.cpp" "Display.cpp" "Touch.cpp" "Gui.cpp" PRIV_REQUIRES spi_flash esp_driver_ppa esp_lcd - REQUIRES esp_mm esp_driver_ppa esp_timer lvgl + REQUIRES esp_mm esp_driver_ppa esp_timer lvgl knx INCLUDE_DIRS "") diff --git a/main/Display.cpp b/main/Display.cpp new file mode 100644 index 0000000..d7d6d45 --- /dev/null +++ b/main/Display.cpp @@ -0,0 +1,101 @@ +#include "Display.hpp" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" +#include "esp_lcd_jd9365_10_1.h" +#include "esp_lv_adapter.h" + +// Display config +#define LCD_H_RES 800 +#define LCD_V_RES 1280 +#define LCD_BIT_PER_PIXEL 16 // RGB565 for better performance +#define LCD_RST_GPIO 27 +#define LCD_BK_GPIO 26 +#define LCD_BK_ON_LEVEL 1 + +// MIPI DSI config +#define MIPI_DSI_LANE_NUM 2 +#define MIPI_DSI_PHY_LDO_CHAN 3 +#define MIPI_DSI_PHY_LDO_VOLTAGE_MV 2500 + +Display::Display() : panel_handle(nullptr) {} + +void Display::init() { + // --- Backlight Init --- + gpio_config_t bk_gpio_config = {}; + bk_gpio_config.mode = GPIO_MODE_OUTPUT; + bk_gpio_config.pin_bit_mask = 1ULL << LCD_BK_GPIO; + gpio_config(&bk_gpio_config); + gpio_set_level(static_cast(LCD_BK_GPIO), LCD_BK_ON_LEVEL); + + // --- DSI PHY LDO Init --- + esp_ldo_channel_handle_t ldo_mipi = NULL; + esp_ldo_channel_config_t ldo_cfg = {}; + ldo_cfg.chan_id = MIPI_DSI_PHY_LDO_CHAN; + ldo_cfg.voltage_mv = MIPI_DSI_PHY_LDO_VOLTAGE_MV; + esp_ldo_acquire_channel(&ldo_cfg, &ldo_mipi); + + // --- DSI Bus Init --- + esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; + esp_lcd_dsi_bus_config_t bus_cfg = {}; + bus_cfg.bus_id = 0; + bus_cfg.num_data_lanes = 2; + bus_cfg.phy_clk_src = static_cast(0); + bus_cfg.lane_bit_rate_mbps = 1500; + esp_lcd_new_dsi_bus(&bus_cfg, &mipi_dsi_bus); + + // --- DSI Panel IO Init --- + esp_lcd_panel_io_handle_t mipi_dbi_io = NULL; + esp_lcd_dbi_io_config_t io_cfg = {}; + io_cfg.virtual_channel = 0; + io_cfg.lcd_cmd_bits = 8; + io_cfg.lcd_param_bits = 8; + esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &io_cfg, &mipi_dbi_io); + + // --- DPI Panel Config --- + esp_lcd_dpi_panel_config_t dpi_cfg = {}; + dpi_cfg.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT; + dpi_cfg.dpi_clock_freq_mhz = 80; + dpi_cfg.virtual_channel = 0; + dpi_cfg.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565; + dpi_cfg.video_timing.h_size = 800; + dpi_cfg.video_timing.v_size = 1280; + dpi_cfg.video_timing.hsync_back_porch = 20; + dpi_cfg.video_timing.hsync_pulse_width = 20; + dpi_cfg.video_timing.hsync_front_porch = 40; + dpi_cfg.video_timing.vsync_back_porch = 10; + dpi_cfg.video_timing.vsync_pulse_width = 4; + dpi_cfg.video_timing.vsync_front_porch = 30; + dpi_cfg.flags.use_dma2d = true; + dpi_cfg.num_fbs = esp_lv_adapter_get_required_frame_buffer_count( + ESP_LV_ADAPTER_TEAR_AVOID_MODE_DEFAULT_MIPI_DSI, + ESP_LV_ADAPTER_ROTATE_90 + ); + + // --- Vendor Config --- + jd9365_vendor_config_t vendor_cfg = {}; + vendor_cfg.flags.use_mipi_interface = 1; + vendor_cfg.mipi_config.dsi_bus = mipi_dsi_bus; + vendor_cfg.mipi_config.dpi_config = &dpi_cfg; + vendor_cfg.mipi_config.lane_num = MIPI_DSI_LANE_NUM; + + // --- Panel Device Config --- + esp_lcd_panel_dev_config_t panel_cfg = {}; + panel_cfg.reset_gpio_num = LCD_RST_GPIO; + panel_cfg.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_cfg.bits_per_pixel = LCD_BIT_PER_PIXEL; + panel_cfg.vendor_config = &vendor_cfg; + + esp_lcd_new_panel_jd9365(mipi_dbi_io, &panel_cfg, &this->panel_handle); + + esp_lcd_panel_reset(this->panel_handle); + esp_lcd_panel_init(this->panel_handle); + esp_lcd_panel_disp_on_off(this->panel_handle, true); +} + +esp_lcd_panel_handle_t Display::getPanelHandle() const { + return this->panel_handle; +} diff --git a/main/Display.hpp b/main/Display.hpp new file mode 100644 index 0000000..493d319 --- /dev/null +++ b/main/Display.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "esp_lcd_panel_ops.h" + +class Display { +public: + Display(); + void init(); + esp_lcd_panel_handle_t getPanelHandle() const; + +private: + esp_lcd_panel_handle_t panel_handle; +}; diff --git a/main/Gui.cpp b/main/Gui.cpp new file mode 100644 index 0000000..4049269 --- /dev/null +++ b/main/Gui.cpp @@ -0,0 +1,46 @@ +#include "Gui.hpp" +#include "esp_lv_adapter.h" + +lv_obj_t * Gui::label2 = nullptr; +lv_obj_t * label = nullptr; + +static void event_handler(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + + if(code == LV_EVENT_CLICKED) { + lv_label_set_text(label, "clicked"); + } + else if(code == LV_EVENT_VALUE_CHANGED) { + LV_LOG_USER("Toggled"); + } +} + +void Gui::create() +{ + if (esp_lv_adapter_lock(-1) == ESP_OK) { + + + + lv_obj_t * btn1 = lv_btn_create(lv_scr_act()); + lv_obj_add_event_cb(btn1, event_handler, LV_EVENT_ALL, NULL); + lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -40); + + label = lv_label_create(btn1); + lv_label_set_text(label, "Button"); + lv_obj_center(label); + + lv_obj_t * btn2 = lv_btn_create(lv_scr_act()); + lv_obj_add_event_cb(btn2, event_handler, LV_EVENT_ALL, NULL); + lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 40); + lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE); + lv_obj_set_height(btn2, LV_SIZE_CONTENT); + + label = lv_label_create(btn2); + lv_label_set_text(label, "Toggle"); + lv_obj_center(label); + + + esp_lv_adapter_unlock(); + } +} diff --git a/main/Gui.hpp b/main/Gui.hpp new file mode 100644 index 0000000..0754e4d --- /dev/null +++ b/main/Gui.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "lvgl.h" + +class Gui { +public: + void create(); + +private: + static void slider_event_cb(lv_event_t * e); + static lv_obj_t * label2; +}; diff --git a/main/KnxWorker.cpp b/main/KnxWorker.cpp new file mode 100644 index 0000000..39fc637 --- /dev/null +++ b/main/KnxWorker.cpp @@ -0,0 +1,24 @@ +#include "KnxWorker.hpp" +#include "esp32_idf_platform.h" +#include "knx_facade.h" +#include "knx/bau07B0.h" +#include "knx/group_object.h" + +#define MASK_VERSION 0x07B0 + +Esp32IdfPlatform knxPlatform(UART_NUM_1); // Use UART_NUM_1, change if needed +Bau07B0 knxBau(knxPlatform); +KnxFacade knx(knxBau); + +KnxWorker::KnxWorker() {} + +void KnxWorker::init() { + knxPlatform.knxUartPins(38, 37); // Set RX=16, TX=17 + knxPlatform.knxUartBaudRate(19200); // Set baud rate (can be changed to other values like 9600, 38400, etc.) + knxPlatform.setupUart(); + +} + +void KnxWorker::toggleProgMode() { + knx.toggleProgMode(); +} \ No newline at end of file diff --git a/main/KnxWorker.hpp b/main/KnxWorker.hpp new file mode 100644 index 0000000..296f701 --- /dev/null +++ b/main/KnxWorker.hpp @@ -0,0 +1,8 @@ +#pragma once + +class KnxWorker { +public: + KnxWorker(); + void init(); + void toggleProgMode(); +}; diff --git a/main/Nvs.cpp b/main/Nvs.cpp new file mode 100644 index 0000000..c2ffa2a --- /dev/null +++ b/main/Nvs.cpp @@ -0,0 +1,15 @@ +#include "Nvs.hpp" +#include "esp_err.h" +#include "nvs_flash.h" +#include "esp_log.h" + +Nvs::Nvs() {} + +void Nvs::init() { + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); +} \ No newline at end of file diff --git a/main/Nvs.hpp b/main/Nvs.hpp new file mode 100644 index 0000000..8a5326f --- /dev/null +++ b/main/Nvs.hpp @@ -0,0 +1,7 @@ +#pragma once + +class Nvs { +public: + Nvs(); + void init(); +}; diff --git a/main/Touch.cpp b/main/Touch.cpp new file mode 100644 index 0000000..ff733c2 --- /dev/null +++ b/main/Touch.cpp @@ -0,0 +1,81 @@ +#include "Touch.hpp" +#include "driver/i2c_master.h" +#include "esp_log.h" +#include "esp_lcd_touch_gt911.h" + +// Common display resolutions, used for touch dimensions +#define LCD_H_RES 800 +#define LCD_V_RES 1280 + +// Touch config (GT911) +#define TOUCH_I2C_SDA 7 +#define TOUCH_I2C_SCL 8 +#define TOUCH_INT_GPIO 3 +#define TOUCH_RST_GPIO 2 +#define TOUCH_I2C_FREQ_HZ 400000 + +Touch::Touch() : touch_handle(nullptr) {} + +void Touch::init() { + // --- I2C Bus Config --- + i2c_master_bus_config_t i2c_bus_cfg = {}; + i2c_bus_cfg.i2c_port = I2C_NUM_0; + i2c_bus_cfg.sda_io_num = static_cast(TOUCH_I2C_SDA); + i2c_bus_cfg.scl_io_num = static_cast(TOUCH_I2C_SCL); + i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_cfg.glitch_ignore_cnt = 7; + i2c_bus_cfg.flags.enable_internal_pullup = true; + + i2c_master_bus_handle_t i2c_bus = NULL; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus)); + + // --- Touch Panel IO Config --- + esp_lcd_panel_io_handle_t touch_io = NULL; + esp_lcd_panel_io_i2c_config_t touch_io_cfg = {}; + touch_io_cfg.dev_addr = 0x5D; // Default GT911 address + touch_io_cfg.control_phase_bytes = 1; + touch_io_cfg.dc_bit_offset = 0; + touch_io_cfg.lcd_cmd_bits = 16; + touch_io_cfg.flags.disable_control_phase = true; + touch_io_cfg.scl_speed_hz = TOUCH_I2C_FREQ_HZ; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &touch_io_cfg, &touch_io)); + + // --- Touch Device Config --- + esp_lcd_touch_config_t touch_cfg = {}; + touch_cfg.x_max = LCD_H_RES; + touch_cfg.y_max = LCD_V_RES; + touch_cfg.rst_gpio_num = static_cast(TOUCH_RST_GPIO); + touch_cfg.int_gpio_num = static_cast(TOUCH_INT_GPIO); + touch_cfg.levels.reset = 0; + touch_cfg.levels.interrupt = 0; + touch_cfg.flags.swap_xy = 1; + touch_cfg.flags.mirror_x = 1; + touch_cfg.flags.mirror_y = 0; + + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(touch_io, &touch_cfg, &this->touch_handle)); +} + +esp_lcd_touch_handle_t Touch::getTouchHandle() const { + return this->touch_handle; +} + +void Touch::lv_indev_read_cb(lv_indev_t *indev, lv_indev_data_t *data) +{ + Touch* self = static_cast(lv_indev_get_user_data(indev)); + if (!self) { + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + uint16_t x[1], y[1]; + uint8_t touch_cnt = 0; + + esp_lcd_touch_read_data(self->getTouchHandle()); + if (esp_lcd_touch_get_coordinates(self->getTouchHandle(), x, y, NULL, &touch_cnt, 1) && touch_cnt > 0) { + data->point.x = x[0]; + data->point.y = y[0]; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} diff --git a/main/Touch.hpp b/main/Touch.hpp new file mode 100644 index 0000000..e463e77 --- /dev/null +++ b/main/Touch.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "esp_lcd_touch.h" +#include "lvgl.h" // Needed for lv_indev_t, lv_indev_data_t + +class Touch { +public: + Touch(); + void init(); + esp_lcd_touch_handle_t getTouchHandle() const; + + static void lv_indev_read_cb(lv_indev_t *indev, lv_indev_data_t *data); + +private: + esp_lcd_touch_handle_t touch_handle; +}; diff --git a/main/hello_world_main.c b/main/hello_world_main.c deleted file mode 100644 index 07e2b5a..0000000 --- a/main/hello_world_main.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 User - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "driver/gpio.h" -#include "driver/i2c_master.h" -#include "esp_log.h" -#include "esp_heap_caps.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_mipi_dsi.h" -#include "esp_ldo_regulator.h" -#include "esp_lcd_touch_gt911.h" -#include "lvgl.h" -#include "esp_timer.h" -#include "esp_lv_adapter.h" -#include "esp_lcd_jd9365_10_1.h" - -#define TAG "JD9365_LVGL" - -// Display config -#define LCD_H_RES 800 -#define LCD_V_RES 1280 -#define LCD_BIT_PER_PIXEL 16 // RGB565 for better performance -#define LCD_RST_GPIO 27 -#define LCD_BK_GPIO 26 -#define LCD_BK_ON_LEVEL 1 - -// MIPI DSI config -#define MIPI_DSI_LANE_NUM 2 -#define MIPI_DSI_PHY_LDO_CHAN 3 -#define MIPI_DSI_PHY_LDO_VOLTAGE_MV 2500 - -// Touch config (GT911) -#define TOUCH_I2C_SDA 7 -#define TOUCH_I2C_SCL 8 -#define TOUCH_INT_GPIO 3 -#define TOUCH_RST_GPIO 2 -#define TOUCH_I2C_FREQ_HZ 400000 - -static esp_lcd_panel_handle_t panel_handle = NULL; -static esp_lcd_touch_handle_t touch_handle = NULL; -static lv_display_t *display = NULL; -static uint8_t *buf1 = NULL; -static uint8_t *buf2 = NULL; - -// ================= Callbacks =================== -static bool on_color_trans_done(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *event_data, void *user_ctx) -{ - if (display) { - lv_display_flush_ready(display); - } - return false; -} - - -static void lvgl_touch_cb(lv_indev_t *indev, lv_indev_data_t *data) -{ - uint16_t x[1], y[1]; - uint8_t touch_cnt = 0; - - esp_lcd_touch_read_data(touch_handle); - if (esp_lcd_touch_get_coordinates(touch_handle, x, y, NULL, &touch_cnt, 1) && touch_cnt > 0) { - data->point.x = x[0]; - data->point.y = y[0]; - data->state = LV_INDEV_STATE_PRESSED; - } else { - data->state = LV_INDEV_STATE_RELEASED; - } -} - -// ================= LCD init =================== -static void lcd_init(void) -{ - gpio_config_t bk_gpio = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << LCD_BK_GPIO }; - gpio_config(&bk_gpio); - gpio_set_level(LCD_BK_GPIO, LCD_BK_ON_LEVEL); - - esp_ldo_channel_handle_t ldo_mipi = NULL; - esp_ldo_channel_config_t ldo_cfg = { .chan_id = MIPI_DSI_PHY_LDO_CHAN, .voltage_mv = MIPI_DSI_PHY_LDO_VOLTAGE_MV }; - esp_ldo_acquire_channel(&ldo_cfg, &ldo_mipi); - - esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; - esp_lcd_dsi_bus_config_t bus_cfg = JD9365_PANEL_BUS_DSI_2CH_CONFIG(); - esp_lcd_new_dsi_bus(&bus_cfg, &mipi_dsi_bus); - - esp_lcd_panel_io_handle_t mipi_dbi_io = NULL; - esp_lcd_dbi_io_config_t io_cfg = JD9365_PANEL_IO_DBI_CONFIG(); - esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &io_cfg, &mipi_dbi_io); - - esp_lcd_dpi_panel_config_t dpi_cfg = JD9365_800_1280_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB565); - dpi_cfg.num_fbs = esp_lv_adapter_get_required_frame_buffer_count( - ESP_LV_ADAPTER_TEAR_AVOID_MODE_DEFAULT_MIPI_DSI, // Tearing mode - ESP_LV_ADAPTER_ROTATE_90 // Rotation - ); - jd9365_vendor_config_t vendor_cfg = { - .flags = { .use_mipi_interface = 1 }, - .mipi_config = { .dsi_bus = mipi_dsi_bus, .dpi_config = &dpi_cfg, .lane_num = MIPI_DSI_LANE_NUM } - }; - esp_lcd_panel_dev_config_t panel_cfg = { - .reset_gpio_num = LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = LCD_BIT_PER_PIXEL, - .vendor_config = &vendor_cfg - }; - esp_lcd_new_panel_jd9365(mipi_dbi_io, &panel_cfg, &panel_handle); - - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - ESP_ERROR_CHECK(esp_lcd_dpi_panel_get_frame_buffer(panel_handle, 2, (void **)&buf1, (void **)&buf2)); - esp_lcd_panel_disp_on_off(panel_handle, true); - - -} - -// ================= Touch init =================== -static void touch_init(void) -{ - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = TOUCH_I2C_SDA, - .scl_io_num = TOUCH_I2C_SCL, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .flags.enable_internal_pullup = true, - }; - i2c_master_bus_handle_t i2c_bus = NULL; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus)); - - esp_lcd_panel_io_handle_t touch_io = NULL; - esp_lcd_panel_io_i2c_config_t touch_io_cfg = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); - touch_io_cfg.scl_speed_hz = TOUCH_I2C_FREQ_HZ; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &touch_io_cfg, &touch_io)); - - esp_lcd_touch_config_t touch_cfg = { - .x_max = LCD_H_RES, - .y_max = LCD_V_RES, - .rst_gpio_num = TOUCH_RST_GPIO, - .int_gpio_num = TOUCH_INT_GPIO, - .levels = { .reset = 0, .interrupt = 0 }, - .flags = { .swap_xy = 1, .mirror_x = 1, .mirror_y = 0 }, - }; - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(touch_io, &touch_cfg, &touch_handle)); -} - -// ================= LVGL init =================== -static void lvgl_init(void) -{ - - esp_lv_adapter_config_t cfg = ESP_LV_ADAPTER_DEFAULT_CONFIG(); - ESP_ERROR_CHECK(esp_lv_adapter_init(&cfg)); - - esp_lv_adapter_display_config_t disp_cfg = ESP_LV_ADAPTER_DISPLAY_MIPI_DEFAULT_CONFIG( - panel_handle, // LCD panel handle - NULL, // LCD panel IO handle (can be NULL for some interfaces) - LCD_H_RES, // Horizontal resolution - LCD_V_RES, // Vertical resolution - ESP_LV_ADAPTER_ROTATE_90 // Rotation - ); - display = esp_lv_adapter_register_display(&disp_cfg); - assert(display != NULL); - - esp_lv_adapter_touch_config_t touch_cfg = ESP_LV_ADAPTER_TOUCH_DEFAULT_CONFIG(display, touch_handle); - lv_indev_t *touch = esp_lv_adapter_register_touch(&touch_cfg); - lv_indev_set_read_cb(touch, lvgl_touch_cb); - assert(touch != NULL); - - ESP_ERROR_CHECK(esp_lv_adapter_start()); -} - -static lv_obj_t * label2; - -static void slider_event_cb(lv_event_t * e) -{ - lv_obj_t * slider = lv_event_get_target_obj(e); - - /*Refresh the text*/ - lv_label_set_text_fmt(label2, "%" LV_PRId32, lv_slider_get_value(slider)); - //lv_obj_align_to(label2, slider, LV_ALIGN_OUT_TOP_MID, 0, -15); /*Align top of the slider*/ -} - -uint32_t my_get_milliseconds() -{ - return esp_timer_get_time() / 1000; -} - -// ================= UI =================== -static void create_ui(void) -{ - if (esp_lv_adapter_lock(-1) == ESP_OK) { - lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); - - // Page 1 - Blue - lv_obj_t *tile1 = lv_tileview_add_tile(tileview, 0, 0, LV_DIR_BOTTOM); - lv_obj_set_style_bg_color(tile1, lv_color_hex(0x2196F3), 0); - lv_obj_t *label1 = lv_label_create(tile1); - lv_label_set_text(label1, "Seite 1\n\nSwipe nach oben"); - lv_obj_set_style_text_color(label1, lv_color_black(), 0); - lv_obj_set_style_text_font(label1, &lv_font_montserrat_14, 0); - lv_obj_center(label1); - - lv_obj_t * slider = lv_slider_create(tile1); - lv_obj_set_width(slider, 200); /*Set the width*/ - lv_obj_center(slider); /*Align to the center of the parent (screen)*/ - lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); - - // Page 2 - Green - lv_obj_t *tile2 = lv_tileview_add_tile(tileview, 0, 1, LV_DIR_TOP); - lv_obj_set_style_bg_color(tile2, lv_color_hex(0x4CAF50), 0); - label2 = lv_label_create(tile2); - lv_label_set_text(label2, "Seite 2\n\nSwipe nach unten"); - lv_obj_set_style_text_color(label2, lv_color_black(), 0); - lv_obj_set_style_text_font(label2, &lv_font_montserrat_14, 0); - lv_obj_center(label2); - esp_lv_adapter_unlock(); - } -} - -// ================= Main =================== -void app_main(void) -{ - ESP_LOGI(TAG, "Starting JD9365 + LVGL 9 (Portrait RGB565)"); - - lcd_init(); - touch_init(); - - lvgl_init(); - create_ui(); - //lv_tick_set_cb(xTaskGetTickCount); - //lv_tick_set_cb(my_get_milliseconds); - while (1) { - lv_timer_handler(); - vTaskDelay(pdMS_TO_TICKS(10)); - } -} diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..3239345 --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,76 @@ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_lv_adapter.h" +#include "lvgl.h" +#include "Display.hpp" +#include "Touch.hpp" +#include "Gui.hpp" +#include "Nvs.hpp" +#include "KnxWorker.hpp" + +#define TAG "App" + + +// This is a simple wrapper for the application logic +class Application { +public: + void init() { + // Initialize hardware + nvs.init(); + display.init(); + touch.init(); + + // Initialize LVGL adapter + esp_lv_adapter_config_t cfg = ESP_LV_ADAPTER_DEFAULT_CONFIG(); + ESP_ERROR_CHECK(esp_lv_adapter_init(&cfg)); + + // Register display + esp_lv_adapter_display_config_t disp_cfg = ESP_LV_ADAPTER_DISPLAY_MIPI_DEFAULT_CONFIG( + display.getPanelHandle(), + NULL, + 800, // Horizontal resolution + 1280, // Vertical resolution + ESP_LV_ADAPTER_ROTATE_90 // Rotation + ); + lv_disp_t* lv_display = esp_lv_adapter_register_display(&disp_cfg); + assert(lv_display != NULL); + + // Register touch + esp_lv_adapter_touch_config_t touch_cfg = ESP_LV_ADAPTER_TOUCH_DEFAULT_CONFIG(lv_display, touch.getTouchHandle()); + lv_indev_t* lv_touch = esp_lv_adapter_register_touch(&touch_cfg); + assert(lv_touch != NULL); + lv_indev_set_user_data(lv_touch, &touch); // Set 'this' Touch object as user data + lv_indev_set_read_cb(lv_touch, Touch::lv_indev_read_cb); // Register the static callback + + ESP_ERROR_CHECK(esp_lv_adapter_start()); + + knxWorker.init(); + } + + void run() { + ESP_LOGI(TAG, "Creating UI"); + gui.create(); + + ESP_LOGI(TAG, "Application running"); + while (true) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + } + +private: + Display display; + Touch touch; + Gui gui; + Nvs nvs; + KnxWorker knxWorker; +}; + +extern "C" void app_main(void) +{ + ESP_LOGI(TAG, "Starting Application"); + Application app; + app.init(); + app.run(); +} \ No newline at end of file