diff --git a/shard.yml b/shard.yml index df5c96b..2c12dda 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: knx -version: 1.0.2 +version: 1.0.3 crystal: ">= 0.36.1" dependencies: diff --git a/src/knx/address.cr b/src/knx/address.cr index 3bd6e55..352917a 100644 --- a/src/knx/address.cr +++ b/src/knx/address.cr @@ -76,7 +76,8 @@ class KNX bits 4, :main_group bits 3, :middle_group end - uint8 :sub_group + + field sub_group : UInt8 def to_s : String "#{main_group}/#{middle_group}/#{sub_group}" @@ -129,7 +130,7 @@ class KNX bits 4, :area_address bits 4, :line_address end - uint8 :device_address + field device_address : UInt8 def to_s : String "#{area_address}.#{line_address}.#{device_address}" diff --git a/src/knx/cemi.cr b/src/knx/cemi.cr index 084b639..d5e3057 100644 --- a/src/knx/cemi.cr +++ b/src/knx/cemi.cr @@ -87,7 +87,7 @@ class KNX NumberedControl = 0b11 end - enum MsgCode + enum MsgCode : UInt8 RawRequest = 0x10 DataRequest = 0x11 PollDataRequest = 0x13 @@ -195,9 +195,9 @@ class KNX class CEMI < BinData endian :big - enum_field UInt8, msg_code : MsgCode = MsgCode::RawRequest - uint8 :info_length, value: ->{ additional_info.size } - bytes :additional_info, length: ->{ info_length } + field msg_code : MsgCode = MsgCode::RawRequest + field info_length : UInt8, value: ->{ additional_info.size } + field additional_info : Bytes, length: ->{ info_length } # --------------------- # Control Fields @@ -241,7 +241,7 @@ class KNX bits 1, :_reserved_ # default: 0_u8 bool no_repeat bool broadcast - enum_bits 2, priority : Priority = Priority::LOW + bits 2, priority : Priority = Priority::LOW bool ack_requested bool is_error @@ -262,10 +262,10 @@ class KNX end # When sending, setting the source address to 0 allows the router to configure - bytes :source_address, length: ->{ 2 } - bytes :destination_address, length: ->{ 2 } + field source_address : Bytes, length: ->{ 2 } + field destination_address : Bytes, length: ->{ 2 } - uint8 :data_length + field data_length : UInt8 # In the Common EMI frame, the APDU payload is defined as follows: @@ -304,7 +304,7 @@ class KNX # +-----------------------------------------------------------------------++-------------.... bit_field do # transport protocol control information - enum_bits 2, tpci : TpciType = TpciType::UnnumberedData + bits 2, tpci : TpciType = TpciType::UnnumberedData bits 4, :tpci_seq_num # Sequence number when tpci is sequenced bits 4, :apci # application protocol control information (What we trying to do: Read, write, respond etc) bits 6, :data # Or the tail end of APCI depending on the message type diff --git a/src/knx/connection/connect_request.cr b/src/knx/connection/connect_request.cr index 55540d5..da26c4c 100644 --- a/src/knx/connection/connect_request.cr +++ b/src/knx/connection/connect_request.cr @@ -7,10 +7,10 @@ class KNX class ConnectRequest < BinData endian big - custom header : Header = Header.new - custom control_endpoint : HPAI = HPAI.new - custom data_endpoint : HPAI = HPAI.new - custom cri : CRI = CRI.new + field header : Header = Header.new + field control_endpoint : HPAI = HPAI.new + field data_endpoint : HPAI = HPAI.new + field cri : CRI = CRI.new def self.new( control : Socket::IPAddress, @@ -36,9 +36,9 @@ class KNX class ConnectResponse < BinData endian big - custom header : Header = Header.new - custom error : ErrorStatus = ErrorStatus.new - custom control_endpoint : HPAI = HPAI.new - custom crd : CRD = CRD.new + field header : Header = Header.new + field error : ErrorStatus = ErrorStatus.new + field control_endpoint : HPAI = HPAI.new + field crd : CRD = CRD.new end end diff --git a/src/knx/connection/connect_state_request.cr b/src/knx/connection/connect_state_request.cr index 5f9cd0d..d493490 100644 --- a/src/knx/connection/connect_state_request.cr +++ b/src/knx/connection/connect_state_request.cr @@ -10,10 +10,10 @@ class KNX class ConnectStateRequest < BinData endian big - custom header : Header = Header.new - uint8 channel_id - enum_field UInt8, status : ConnectionError = ConnectionError::NoError - custom control_endpoint : HPAI = HPAI.new + field header : Header = Header.new + field channel_id : UInt8 + field status : ConnectionError = ConnectionError::NoError + field control_endpoint : HPAI = HPAI.new def self.new( channel_id : Int, @@ -33,8 +33,8 @@ class KNX class ConnectStateResponse < BinData endian big - custom header : Header = Header.new - uint8 channel_id - enum_field UInt8, status : ConnectionError = ConnectionError::NoError + field header : Header = Header.new + field channel_id : UInt8 + field status : ConnectionError = ConnectionError::NoError end end diff --git a/src/knx/connection/crd.cr b/src/knx/connection/crd.cr index 727ac26..d887301 100644 --- a/src/knx/connection/crd.cr +++ b/src/knx/connection/crd.cr @@ -5,8 +5,8 @@ class KNX LENGTH = 4 - uint8 length, value: ->{ 4 } - enum_field UInt8, connect_type : ConnectType = ConnectType::Tunnel - uint16 identifier + field length : UInt8, value: ->{ 4 } + field connect_type : ConnectType = ConnectType::Tunnel + field identifier : UInt16 end end diff --git a/src/knx/connection/cri.cr b/src/knx/connection/cri.cr index 120c9f2..59ed63a 100644 --- a/src/knx/connection/cri.cr +++ b/src/knx/connection/cri.cr @@ -1,5 +1,5 @@ class KNX - enum ConnectType + enum ConnectType : UInt8 # Data connection used to configure a KNXnet/IP device DeviceManagement = 0x03 @@ -26,8 +26,8 @@ class KNX LENGTH = 4 - uint8 length, value: ->{ 4 } - enum_field UInt8, connect_type : ConnectType = ConnectType::Tunnel + field length : UInt8, value: ->{ 4 } + field connect_type : ConnectType = ConnectType::Tunnel bit_field do bool bus_monitor_tunnel bits 4, :_reserved_ @@ -35,6 +35,6 @@ class KNX bool data_link_tunnel bits 1, :_reserved2_ end - uint8 reserved + field reserved : UInt8 end end diff --git a/src/knx/connection/error_status.cr b/src/knx/connection/error_status.cr index eeab677..d65553b 100644 --- a/src/knx/connection/error_status.cr +++ b/src/knx/connection/error_status.cr @@ -1,5 +1,5 @@ class KNX - enum ConnectionError + enum ConnectionError : UInt8 # The connection state is normal. NoError = 0 @@ -40,7 +40,7 @@ class KNX class ErrorStatus < BinData LENGTH = 2 - uint8 length, value: ->{ 1 } - enum_field UInt8, status : ConnectionError = ConnectionError::NoError + field length : UInt8, value: ->{ 1 } + field status : ConnectionError = ConnectionError::NoError end end diff --git a/src/knx/connection/tunnel_request.cr b/src/knx/connection/tunnel_request.cr index 0570d20..c8f52ba 100644 --- a/src/knx/connection/tunnel_request.cr +++ b/src/knx/connection/tunnel_request.cr @@ -7,13 +7,13 @@ class KNX class TunnelRequest < BinData endian big - custom header : Header = Header.new - uint8 length, value: ->{ 4 } - uint8 channel_id - uint8 sequence - enum_field UInt8, status : ConnectionError = ConnectionError::NoError + field header : Header = Header.new + field length : UInt8, value: ->{ 4 } + field channel_id : UInt8 + field sequence : UInt8 + field status : ConnectionError = ConnectionError::NoError - custom cemi : CEMI = CEMI.new + field cemi : CEMI = CEMI.new def self.new( channel_id : Int, @@ -44,10 +44,10 @@ class KNX class TunnelResponse < BinData endian big - custom header : Header = Header.new - uint8 length, value: ->{ 4 } - uint8 channel_id - uint8 sequence - enum_field UInt8, status : ConnectionError = ConnectionError::NoError + field header : Header = Header.new + field length : UInt8, value: ->{ 4 } + field channel_id : UInt8 + field sequence : UInt8 + field status : ConnectionError = ConnectionError::NoError end end diff --git a/src/knx/discovery/dib.cr b/src/knx/discovery/dib.cr index 8bdd8cc..785ab9a 100644 --- a/src/knx/discovery/dib.cr +++ b/src/knx/discovery/dib.cr @@ -1,5 +1,5 @@ class KNX - enum DescriptionType + enum DescriptionType : UInt8 DeviceInformation = 1 SupportedServiceFamilies IPConfig @@ -8,52 +8,8 @@ class KNX ManufacturerData = 0xFE end - # Generic block - class InformationBlock < BinData - endian :big - - uint8 length - enum_field UInt8, description_type : DescriptionType = DescriptionType::DeviceInformation - - custom device_info : DIB = DIB.new, onlyif: ->{ description_type == DescriptionType::DeviceInformation } - array supported_services : ServiceFamily, length: ->{ (length - 2) // 2 }, onlyif: ->{ description_type == DescriptionType::SupportedServiceFamilies } - - # Ignore data for information we can't parse - bytes raw_data, length: ->{ length - 2 }, onlyif: ->{ - !{ - DescriptionType::DeviceInformation, - DescriptionType::SupportedServiceFamilies, - }.includes?(description_type) - } - end - - # Specific info blocks - class DeviceInfo < BinData - endian :big - - LENGTH = 54 - - uint8 length, value: ->{ 54 } - enum_field UInt8, description_type : DescriptionType = DescriptionType::DeviceInformation - custom info : DIB = DIB.new - - {% for func in [:name, :mac_address, :multicast_address, :serial] %} - def {{func.id}} - @info.{{func.id}} - end - {% end %} - end - - class SupportedServices < BinData - endian :big - - uint8 length, value: ->{ families.size * 2 + 2 } - enum_field UInt8, description_type : DescriptionType = DescriptionType::SupportedServiceFamilies - array families : ServiceFamily, length: ->{ (length - 2) // 2 } - end - @[Flags] - enum MediumType + enum MediumType : UInt8 Reserved TP1 PL110 @@ -62,19 +18,36 @@ class KNX IP end + enum FamilyType : UInt8 + Core = 2 + DeviceManagement + Tunnelling + Routing + RemoteLogging + RemoteConfiguration + ObjectServer + end + + class ServiceFamily < BinData + endian :big + + field family_type : FamilyType = FamilyType::Core + field version : UInt8 + end + # Device Information Block class DIB < BinData endian :big - enum_field UInt8, medium : MediumType = MediumType::IP + field medium : MediumType = MediumType::IP # Device status just used to indicate if in programming mode - uint8 device_status - custom source : IndividualAddress = IndividualAddress.new - uint16 project_installation_id - bytes device_serial, length: ->{ 6 } - bytes device_multicast_address, length: ->{ 4 } - bytes device_mac_address, length: ->{ 6 } - string friendly_name, length: ->{ 30 } + field device_status : UInt8 + field source : IndividualAddress = IndividualAddress.new + field project_installation_id : UInt16 + field device_serial : Bytes, length: ->{ 6 } + field device_multicast_address : Bytes, length: ->{ 4 } + field device_mac_address : Bytes, length: ->{ 6 } + field friendly_name : String, length: ->{ 30 } def programming_mode? @device_status > 0 @@ -97,20 +70,47 @@ class KNX end end - enum FamilyType - Core = 2 - DeviceManagement - Tunnelling - Routing - RemoteLogging - RemoteConfiguration - ObjectServer + # Generic block + class InformationBlock < BinData + endian :big + + field length : UInt8 + field description_type : DescriptionType = DescriptionType::DeviceInformation + + field device_info : DIB = DIB.new, onlyif: ->{ description_type == DescriptionType::DeviceInformation } + field supported_services : Array(ServiceFamily), length: ->{ (length - 2) // 2 }, onlyif: ->{ description_type == DescriptionType::SupportedServiceFamilies } + + # Ignore data for information we can't parse + field raw_data : Bytes, length: ->{ length - 2 }, onlyif: ->{ + !{ + DescriptionType::DeviceInformation, + DescriptionType::SupportedServiceFamilies, + }.includes?(description_type) + } end - class ServiceFamily < BinData + # Specific info blocks + class DeviceInfo < BinData + endian :big + + LENGTH = 54 + + field length : UInt8, value: ->{ 54 } + field description_type : DescriptionType = DescriptionType::DeviceInformation + field info : DIB = DIB.new + + {% for func in [:name, :mac_address, :multicast_address, :serial] %} + def {{func.id}} + @info.{{func.id}} + end + {% end %} + end + + class SupportedServices < BinData endian :big - enum_field UInt8, family_type : FamilyType = FamilyType::Core - uint8 version + field length : UInt8, value: ->{ families.size * 2 + 2 } + field description_type : DescriptionType = DescriptionType::SupportedServiceFamilies + field families : Array(ServiceFamily), length: ->{ (length - 2) // 2 } end end diff --git a/src/knx/discovery/hpai.cr b/src/knx/discovery/hpai.cr index c8ddc99..6f8787c 100644 --- a/src/knx/discovery/hpai.cr +++ b/src/knx/discovery/hpai.cr @@ -1,5 +1,5 @@ class KNX - enum ProtocolType + enum ProtocolType : UInt8 IPv4UDP = 1 IPv4TCP = 2 end @@ -10,10 +10,10 @@ class KNX LENGTH = 8 - uint8 length, default: 8_u8 - enum_field UInt8, protocol : ProtocolType = ProtocolType::IPv4UDP - bytes ip_addr, length: ->{ 4 } - uint16 port + field length : UInt8 = 8_u8 + field protocol : ProtocolType = ProtocolType::IPv4UDP + field ip_addr : Bytes, length: ->{ 4 } + field port : UInt16 def self.new(ip : Socket::IPAddress) hpai = HPAI.new diff --git a/src/knx/discovery/search_request.cr b/src/knx/discovery/search_request.cr index b51b464..3a5a502 100644 --- a/src/knx/discovery/search_request.cr +++ b/src/knx/discovery/search_request.cr @@ -8,8 +8,8 @@ class KNX class SearchRequest < BinData endian big - custom header : Header = Header.new - custom address : HPAI = HPAI.new + field header : Header = Header.new + field address : HPAI = HPAI.new def self.new(ip : Socket::IPAddress, protocol : ProtocolType = ProtocolType::IPv4UDP) request = SearchRequest.new @@ -41,9 +41,9 @@ class KNX class SearchResponse < BinData endian big - custom header : Header = Header.new - custom address : HPAI = HPAI.new - custom device : DeviceInfo = DeviceInfo.new - custom services : SupportedServices = SupportedServices.new + field header : Header = Header.new + field address : HPAI = HPAI.new + field device : DeviceInfo = DeviceInfo.new + field services : SupportedServices = SupportedServices.new end end diff --git a/src/knx/header.cr b/src/knx/header.cr index 2ea34cc..b3b2537 100644 --- a/src/knx/header.cr +++ b/src/knx/header.cr @@ -1,7 +1,7 @@ # frozen_string_literal: true, encoding: ASCII-8BIT class KNX - enum RequestTypes + enum RequestTypes : UInt16 SearchRequest = 0x0201 SearchResponse = 0x0202 DescriptionRequest = 0x0203 @@ -43,31 +43,33 @@ class KNX class Header < BinData endian big - uint8 :header_length, default: 0x06 # Length 6 (always for version 1) - uint8 :version, default: 0x10_u8 # version 1 - enum_field UInt16, request_type : RequestTypes = RequestTypes::RoutingIndication - uint16 :request_length + field header_length : UInt8 = 0x06 # Length 6 (always for version 1) + field version : UInt8 = 0x10_u8 # version 1 + field request_type : RequestTypes = RequestTypes::RoutingIndication + field request_length : UInt16 # See: https://youtu.be/UjOBudAG654?t=42m20s group :wrapper, onlyif: ->{ request_type == RequestTypes::SecureWrapper } do - uint16 :session_id # Not sure what this should be + field session_id : UInt16 # Not sure what this should be bit_field do bits 48, :timestamp # Timestamp for multicast messages, sequence number for tunneling bits 48, :knx_serial_number # Serial of the device - random constant end - uint16 :message_tag # Random number + + # Random number + field message_tag : UInt16 # header + security info + cbc_mac == 38 # 6 16 16 == 38 - string :encrypted_frame, length: ->{ parent.request_length - 38 } + field encrypted_frame : String, length: ->{ parent.request_length - 38 } # Encryption: Timestamp + Serial Number + Tag + 0x01 + counter (1 byte), starting at 0 # Single key for each multicast group: PID_BACKBONE_KEY # https://en.wikipedia.org/wiki/CCM_mode # https://en.wikipedia.org/wiki/CBC-MAC (always 128bit (16bytes) in KNX) # Timestamp + Serial Number + Tag + frame length (2 bytes) - string :cmac, length: ->{ 16 } + field cmac : String, length: ->{ 16 } end group :timer, onlyif: ->{ request_type == RequestTypes::SecureTimerNotify } do @@ -75,10 +77,10 @@ class KNX bits 48, :timestamp # Current time as understood by the device bits 48, :knx_serial_number # Serial of the device end - uint16 :message_tag # Random number + field message_tag : UInt16 # Random number # Timestamp + Serial Number + Tag + frame length (2 bytes) == 0x0000 - string :cmac, length: ->{ 16 } + field cmac : String, length: ->{ 16 } end end end diff --git a/src/knx/object_server/datagram.cr b/src/knx/object_server/datagram.cr index c9afbd6..c1fca16 100644 --- a/src/knx/object_server/datagram.cr +++ b/src/knx/object_server/datagram.cr @@ -1,9 +1,9 @@ class KNX class ObjectServer class Datagram < BinData - custom knx_header : KNX::Header = KNX::Header.new - custom connection : ConnectionHeader = ConnectionHeader.new - custom header : ObjectHeader = ObjectHeader.new + field knx_header : KNX::Header = KNX::Header.new + field connection : ConnectionHeader = ConnectionHeader.new + field header : ObjectHeader = ObjectHeader.new def error @header.error diff --git a/src/knx/object_server/item.cr b/src/knx/object_server/item.cr index ab460b7..639725d 100644 --- a/src/knx/object_server/item.cr +++ b/src/knx/object_server/item.cr @@ -20,8 +20,8 @@ class KNX class Item < BinData endian :big - uint16 :id - uint8 :request_flags + field id : UInt16 + field request_flags : UInt8 # Request layout # bit_field do @@ -98,8 +98,8 @@ class KNX value end - uint8 :value_length, value: ->{ value.size.to_u8 } - bytes :value, length: ->{ value_length } + field value_length : UInt8, value: ->{ value.size.to_u8 } + field value : Bytes, length: ->{ value_length } end end end diff --git a/src/knx/object_server/object_header.cr b/src/knx/object_server/object_header.cr index 546cb5a..e1425c4 100644 --- a/src/knx/object_server/object_header.cr +++ b/src/knx/object_server/object_header.cr @@ -1,21 +1,23 @@ +require "./item" + class KNX class ObjectServer class ConnectionHeader < BinData endian :big - uint8 :header_length, default: 0x04 - uint8 :reserved1, default: 0x00 - uint8 :reserved2, default: 0x00 - uint8 :reserved3, default: 0x00 + field header_length : UInt8 = 0x04 + field reserved1 : UInt8 = 0x00 + field reserved2 : UInt8 = 0x00 + field reserved3 : UInt8 = 0x00 end - enum Filter + enum Filter : UInt8 AllValues = 0 ValidValues UpdatedValues end - enum Error + enum Error : UInt8 NoError = 0 DeviceInternalError NoItemFound @@ -33,18 +35,18 @@ class KNX class ObjectHeader < BinData endian :big - uint8 :main_service, default: 0xF0 - uint8 :sub_service - uint16 :start_item - uint16 :item_count + field main_service : UInt8 = 0xF0 + field sub_service : UInt8 + field start_item : UInt16 + field item_count : UInt16 property filter : Filter? - enum_field UInt8, _filter : Filter = Filter::ValidValues, value: ->{ filter ? filter : _filter }, onlyif: ->{ filter } - enum_field UInt8, error : Error = Error::NoError, onlyif: ->{ item_count == 0 } + field _filter : Filter = Filter::ValidValues, value: ->{ filter ? filter : _filter }, onlyif: ->{ filter } + field error : Error = Error::NoError, onlyif: ->{ item_count == 0 } # Requests or Statuses - array items : Item, length: ->{ item_count } + field items : Array(Item), length: ->{ item_count } end end end diff --git a/src/knx/routing/indication_request.cr b/src/knx/routing/indication_request.cr index b415390..bf44083 100644 --- a/src/knx/routing/indication_request.cr +++ b/src/knx/routing/indication_request.cr @@ -2,9 +2,9 @@ class KNX class IndicationRequest < BinData endian big - custom header : Header = Header.new - custom cemi : CEMI = CEMI.new - bytes extended_bytes, length: ->{ header.request_length - 17 - cemi.info_length } + field header : Header = Header.new + field cemi : CEMI = CEMI.new + field extended_bytes : Bytes, length: ->{ header.request_length - 17 - cemi.info_length } def payload : Bytes data_length = @header.request_length - 17 - @cemi.info_length