From 62d557a58dfd5608b8b4d776df204c25505dca5d Mon Sep 17 00:00:00 2001 From: skarg Date: Thu, 16 Aug 2007 18:54:17 +0000 Subject: [PATCH] Added files for minimal compile. --- bacnet-stack/ports/atmega168/device.c | 579 ++++++++++++++++++++++++ bacnet-stack/ports/atmega168/dlmstp.c | 363 +++++++++++++++ bacnet-stack/ports/atmega168/timer1.ods | Bin 0 -> 21252 bytes 3 files changed, 942 insertions(+) create mode 100644 bacnet-stack/ports/atmega168/device.c create mode 100644 bacnet-stack/ports/atmega168/dlmstp.c create mode 100644 bacnet-stack/ports/atmega168/timer1.ods diff --git a/bacnet-stack/ports/atmega168/device.c b/bacnet-stack/ports/atmega168/device.c new file mode 100644 index 00000000..99929bb2 --- /dev/null +++ b/bacnet-stack/ports/atmega168/device.c @@ -0,0 +1,579 @@ +/************************************************************************** +* +* Copyright (C) 2007 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacstr.h" +#include "bacenum.h" +#include "apdu.h" +#include "dcc.h" +#include "dlmstp.h" +#include "rs485.h" +#include "version.h" +/* objects */ +#include "device.h" +#if 0 +#include "ai.h" +#include "av.h" +#include "bi.h" +#include "bv.h" +#include "wp.h" +#include "dcc.h" +#endif + +/* note: you really only need to define variables for + properties that are writable or that may change. + The properties that are constant can be hard coded + into the read-property encoding. */ +static uint32_t Object_Instance_Number = 12345; +static char Object_Name[32] = "ATmega Device"; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; + +BACNET_REINITIALIZED_STATE_OF_DEVICE Reinitialize_State = + REINITIALIZED_STATE_IDLE; + +void Device_Reinit(void) +{ + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); +} + +void Device_Init(void) +{ + Reinitialize_State = REINITIALIZED_STATE_IDLE; + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + /* FIXME: Get the data from the eeprom */ + /* I2C_Read_Block(EEPROM_DEVICE_ADDRESS, + (char *)&Object_Instance_Number, + sizeof(Object_Instance_Number), + EEPROM_BACNET_ID_ADDR); */ +} + +/* methods to manipulate the data */ +uint32_t Device_Object_Instance_Number(void) +{ + return Object_Instance_Number; +} + +bool Device_Set_Object_Instance_Number(uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) { + Object_Instance_Number = object_id; + /* FIXME: Write the data to the eeprom */ + /* I2C_Write_Block( + EEPROM_DEVICE_ADDRESS, + (char *)&Object_Instance_Number, + sizeof(Object_Instance_Number), + EEPROM_BACNET_ID_ADDR); */ + } else + status = false; + + return status; +} + +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + /* BACnet allows for a wildcard instance number */ + return ((Object_Instance_Number == object_id) || + (object_id == BACNET_MAX_INSTANCE)); +} + +BACNET_DEVICE_STATUS Device_System_Status(void) +{ + return System_Status; +} + +void Device_Set_System_Status(BACNET_DEVICE_STATUS status) +{ + if (status < MAX_DEVICE_STATUS) + System_Status = status; +} + +/* FIXME: put your vendor ID here! */ +uint16_t Device_Vendor_Identifier(void) +{ + return 0; +} + +uint8_t Device_Protocol_Version(void) +{ + return 1; +} + +uint8_t Device_Protocol_Revision(void) +{ + return 5; +} + +/* FIXME: MAX_APDU is defined in config.ini - set it! */ +uint16_t Device_Max_APDU_Length_Accepted(void) +{ + return MAX_APDU; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported(void) +{ + return SEGMENTATION_NONE; +} + +uint16_t Device_APDU_Timeout(void) +{ + return 60000; +} + + +uint8_t Device_Number_Of_APDU_Retries(void) +{ + return 0; +} + +uint8_t Device_Database_Revision(void) +{ + return 0; +} + +unsigned Device_Object_List_Count(void) +{ + unsigned count = 1; /* at least 1 for device object */ + + /* FIXME: add objects as needed */ +#if 0 + count += Binary_Input_Count(); + count += Binary_Value_Count(); + count += Analog_Input_Count(); + count += Analog_Value_Count(); +#endif + + return count; +} + +bool Device_Object_List_Identifier(unsigned array_index, + int *object_type, uint32_t * instance) +{ + bool status = false; +#if 0 + unsigned object_index = 0; + unsigned object_count = 0; +#endif + + /* device object */ + if (array_index == 1) { + *object_type = OBJECT_DEVICE; + *instance = Object_Instance_Number; + status = true; + } +#if 0 + /* normalize the index since + we know it is not the previous objects */ + /* array index starts at 1 */ + object_index = array_index - 1; + /* 1 for the device object */ + object_count = 1; + /* FIXME: add objects as needed */ + /* binary value objects */ + if (!status) { + object_index -= object_count; + object_count = Binary_Value_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_BINARY_VALUE; + *instance = Binary_Value_Index_To_Instance(object_index); + status = true; + } + } + /* analog input objects */ + if (!status) { + /* array index starts at 1, and 1 for the device object */ + object_index -= object_count; + object_count = Analog_Value_Count(); + if (object_index < object_count) { + *object_type = OBJECT_ANALOG_VALUE; + *instance = Analog_Value_Index_To_Instance(object_index); + status = true; + } + } + /* analog input objects */ + if (!status) { + /* array index starts at 1, and 1 for the device object */ + object_index -= object_count; + object_count = Analog_Input_Count(); + if (object_index < object_count) { + *object_type = OBJECT_ANALOG_INPUT; + *instance = Analog_Input_Index_To_Instance(object_index); + status = true; + } + } + /* binary input objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Binary_Input_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_BINARY_INPUT; + *instance = Binary_Input_Index_To_Instance(object_index); + status = true; + } + } +#endif + + return status; +} + +/* return the length of the apdu encoded or -1 for error */ +int Device_Encode_Property_APDU(uint8_t * apdu, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int apdu_len = 0; /* return value */ + int len = 0; /* apdu len intermediate value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + unsigned i = 0; + int object_type = 0; + uint32_t instance = 0; + unsigned count = 0; + BACNET_TIME local_time; + BACNET_DATE local_date; + + /* FIXME: change the hardcoded names to suit your application */ + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_DEVICE, + Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + characterstring_init_ansi(&char_string, Object_Name); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_DEVICE); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, "BACnet Demo"); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = + encode_tagged_enumerated(&apdu[0], Device_System_Status()); + break; + case PROP_VENDOR_NAME: + characterstring_init_ansi(&char_string, "ASHRAE"); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Vendor_Identifier()); + break; + case PROP_MODEL_NAME: + characterstring_init_ansi(&char_string, "GNU Demo"); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_FIRMWARE_REVISION: + characterstring_init_ansi(&char_string, BACnet_Version); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_APPLICATION_SOFTWARE_VERSION: + characterstring_init_ansi(&char_string, "1.0"); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_LOCATION: + characterstring_init_ansi(&char_string, "USA"); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Protocol_Version()); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Protocol_Revision()); + break; + /* BACnet Legacy Support */ + case PROP_PROTOCOL_CONFORMANCE_CLASS: + apdu_len = encode_tagged_unsigned(&apdu[0], 1); + break; + case PROP_PROTOCOL_SERVICES_SUPPORTED: + /* Note: list of services that are executed, not initiated. */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) { + /* automatic lookup based on handlers set */ + bitstring_set_bit(&bit_string, (uint8_t) i, + apdu_service_supported(i)); + } + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + /* Note: this is the list of objects that can be in this device, + not a list of objects that this device can access */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) { + /* FIXME: if ReadProperty used an array of Functions... */ + /* initialize all the object types to not-supported */ + bitstring_set_bit(&bit_string, (uint8_t) i, false); + } + /* FIXME: indicate the objects that YOU support */ + bitstring_set_bit(&bit_string, OBJECT_DEVICE, true); +#if 0 + bitstring_set_bit(&bit_string, OBJECT_ANALOG_VALUE, true); + bitstring_set_bit(&bit_string, OBJECT_BINARY_VALUE, true); + bitstring_set_bit(&bit_string, OBJECT_ANALOG_INPUT, true); + bitstring_set_bit(&bit_string, OBJECT_BINARY_INPUT, true); +#endif + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_OBJECT_LIST: + count = Device_Object_List_Count(); + /* Array element zero is the number of objects in the list */ + if (array_index == 0) + apdu_len = encode_tagged_unsigned(&apdu[0], count); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. Note that more than likely you will have */ + /* to return an error if the number of encoded objects exceeds */ + /* your maximum APDU size. */ + else if (array_index == BACNET_ARRAY_ALL) { + for (i = 1; i <= count; i++) { + if (Device_Object_List_Identifier(i, &object_type, + &instance)) { + len = + encode_tagged_object_id(&apdu[apdu_len], + object_type, instance); + apdu_len += len; + /* assume next one is the same size as this one */ + /* can we all fit into the APDU? */ + if ((apdu_len + len) >= MAX_APDU) { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } else { + /* error: internal error? */ + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_OTHER; + apdu_len = -1; + break; + } + } + } else { + if (Device_Object_List_Identifier(array_index, &object_type, + &instance)) + apdu_len = + encode_tagged_object_id(&apdu[0], object_type, + instance); + else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + break; + case PROP_MAX_APDU_LENGTH_ACCEPTED: + apdu_len = encode_tagged_unsigned(&apdu[0], + Device_Max_APDU_Length_Accepted()); + break; + case PROP_SEGMENTATION_SUPPORTED: + apdu_len = encode_tagged_enumerated(&apdu[0], + Device_Segmentation_Supported()); + break; + case PROP_APDU_TIMEOUT: + apdu_len = encode_tagged_unsigned(&apdu[0], Device_APDU_Timeout()); + break; + case PROP_NUMBER_OF_APDU_RETRIES: + apdu_len = + encode_tagged_unsigned(&apdu[0], + Device_Number_Of_APDU_Retries()); + break; + case PROP_DEVICE_ADDRESS_BINDING: + /* FIXME: encode the list here, if it exists */ + break; + case PROP_DATABASE_REVISION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Database_Revision()); + break; + case PROP_MAX_INFO_FRAMES: + apdu_len = + encode_tagged_unsigned(&apdu[0], dlmstp_max_info_frames()); + break; + case PROP_MAX_MASTER: + apdu_len = encode_tagged_unsigned(&apdu[0], dlmstp_max_master()); + break; + case PROP_LOCAL_TIME: + /* FIXME: if you support time */ + local_time.hour = 0; + local_time.min = 0; + local_time.sec = 0; + local_time.hundredths = 0; + apdu_len = encode_tagged_time(&apdu[0], &local_time); + break; + case PROP_UTC_OFFSET: + /* Note: BACnet Time Zone is inverse of everybody else */ + apdu_len = encode_tagged_signed(&apdu[0], 5 /* EST */ ); + break; + case PROP_LOCAL_DATE: + /* FIXME: if you support date */ + local_date.year = 2006; /* AD */ + local_date.month = 4; /* Jan=1..Dec=12 */ + local_date.day = 11; /* 1..31 */ + local_date.wday = 0; /* 1=Mon..7=Sun */ + apdu_len = encode_tagged_date(&apdu[0], &local_date); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + /* FIXME: if you support time/date */ + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case 9600: + apdu_len = encode_tagged_unsigned(&apdu[0], RS485_Get_Baud_Rate()); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + if (!Device_Valid_Object_Instance_Number(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_OBJECT_IDENTIFIER: + if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + if ((value.type.Object_Id.type == OBJECT_DEVICE) && + (Device_Set_Object_Instance_Number(value.type. + Object_Id.instance))) { + /* we could send an I-Am broadcast to let the world know */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_MAX_INFO_FRAMES: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int <= 255) { + dlmstp_set_max_info_frames(value.type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_MAX_MASTER: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if ((value.type.Unsigned_Int > 0) && + (value.type.Unsigned_Int <= 127)) { + dlmstp_set_max_master(value.type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OBJECT_NAME: + if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + uint8_t encoding; + size_t len; + + encoding = + characterstring_encoding(&value.type.Character_String); + len = characterstring_length(&value.type.Character_String); + if (encoding == CHARACTER_ANSI_X34) { + if (len <= 20) { + /* FIXME: set the name */ + /* Display_Set_Name( + characterstring_value(&value.type.Character_String)); */ + /* FIXME: All the object names in a device must be unique. + Disallow setting the Device Object Name to any objects in + the device. */ + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case 9600: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int > 115200) { + RS485_Set_Baud_Rate(value.type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} diff --git a/bacnet-stack/ports/atmega168/dlmstp.c b/bacnet-stack/ports/atmega168/dlmstp.c new file mode 100644 index 00000000..d10af54d --- /dev/null +++ b/bacnet-stack/ports/atmega168/dlmstp.c @@ -0,0 +1,363 @@ +/************************************************************************** +* +* Copyright (C) 2007 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ +#include +#include +#include +#include +#include "bacdef.h" +#include "mstp.h" +#include "dlmstp.h" +#include "rs485.h" +#include "npdu.h" + +/* This file has been customized for use with the ATmega168 */ +#include "hardware.h" + +/* Number of MS/TP Packets Rx/Tx */ +uint16_t MSTP_Packets = 0; + +/* local MS/TP port data - shared with RS-485 */ +static volatile struct mstp_port_struct_t MSTP_Port; +/* receive buffer */ +static DLMSTP_PACKET Receive_Packet; +static DLMSTP_PACKET Transmit_Packet; +/* buffers needed by mstp port struct */ +static uint8_t TxBuffer[MAX_MPDU]; +static uint8_t RxBuffer[MAX_MPDU]; + +#define INCREMENT_AND_LIMIT_UINT16(x) {if (x < 0xFFFF) x++;} + +void dlmstp_millisecond_timer(void) +{ + INCREMENT_AND_LIMIT_UINT16(MSTP_Port.SilenceTimer); +} + +void dlmstp_copy_bacnet_address(BACNET_ADDRESS * dest, BACNET_ADDRESS * src) +{ + int i = 0; + + if (src && dest) { + src->mac_len = dest->mac_len; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->mac[i] = dest->mac[i]; + } + src->net = dest->net; + src->len = dest->len; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = dest->adr[i]; + } + } +} + +bool dlmstp_init(char *ifname) +{ + (void)ifname; + /* initialize packet */ + Receive_Packet.ready = false; + Receive_Packet.pdu_len = 0; + /* initialize hardware */ + RS485_Initialize(); + /* initialize MS/TP data structures */ + MSTP_Port.InputBuffer = &RxBuffer[0]; + MSTP_Port.InputBufferSize = sizeof(RxBuffer); + MSTP_Port.OutputBuffer = &TxBuffer[0]; + MSTP_Port.OutputBufferSize = sizeof(TxBuffer); + MSTP_Init(&MSTP_Port); + + return true; +} + +void dlmstp_cleanup(void) +{ + /* nothing to do for static buffers */ +} + +/* returns number of bytes sent on success, zero on failure */ +int dlmstp_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) /* number of bytes of data */ +{ + int bytes_sent = 0; + unsigned i = 0; /* loop counter */ + + if (Transmit_Packet.ready == false) { + if (npdu_data->data_expecting_reply) { + Transmit_Packet.frame_type = + FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; + } else { + Transmit_Packet.frame_type = + FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; + } + Transmit_Packet.pdu_len = pdu_len; + for (i = 0; i < pdu_len; i++) { + Transmit_Packet.pdu[i] = pdu[i]; + } + dlmstp_copy_bacnet_address(&Transmit_Packet.address, dest); + bytes_sent = sizeof(Transmit_Packet); + } + + return bytes_sent; +} + +static void dlmstp_task(void) +{ + /* only do receive state machine while we don't have a frame */ + if ((MSTP_Port.ReceivedValidFrame == false) && + (MSTP_Port.ReceivedInvalidFrame == false)) { + do { + RS485_Check_UART_Data(&MSTP_Port); + MSTP_Receive_Frame_FSM(&MSTP_Port); + if (MSTP_Port.ReceivedValidFrame || + MSTP_Port.ReceivedInvalidFrame) + break; + } while (MSTP_Port.DataAvailable); + } + /* only do master state machine while rx is idle */ + if (MSTP_Port.receive_state == MSTP_RECEIVE_STATE_IDLE) { + while (MSTP_Master_Node_FSM(&MSTP_Port)) { + /* do nothing */ + }; + } + + return; +} + +/* copy the packet if one is received. + Return the length of the packet */ +uint16_t dlmstp_receive( + BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) /* milliseconds to wait for a packet */ +{ + unsigned i = 0; /* loop counter */ + uint16_t pdu_len = 0; /* return value */ + + dlmstp_task(); + /* see if there is a packet available, and a place + to put the reply (if necessary) and process it */ + if (Receive_Packet.ready) { + if (Receive_Packet.pdu_len) { + MSTP_Packets++; + for (i = 0; i < Receive_Packet.pdu_len; i++) { + pdu[i] = Receive_Packet.pdu[i]; + } + dlmstp_copy_bacnet_address(src, &Receive_Packet.address); + pdu_len = Receive_Packet.pdu_len; + } + Receive_Packet.ready = false; + } + + return pdu_len; +} + +void dlmstp_fill_bacnet_address(BACNET_ADDRESS * src, uint8_t mstp_address) +{ + int i = 0; + + if (mstp_address == MSTP_BROADCAST_ADDRESS) { + /* mac_len = 0 if broadcast address */ + src->mac_len = 0; + src->mac[0] = 0; + } else { + src->mac_len = 1; + src->mac[0] = mstp_address; + } + /* fill with 0's starting with index 1; index 0 filled above */ + for (i = 1; i < MAX_MAC_LEN; i++) { + src->mac[i] = 0; + } + src->net = 0; + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = 0; + } +} + +/* for the MS/TP state machine to use for putting received data */ +uint16_t MSTP_Put_Receive( + volatile struct mstp_port_struct_t *mstp_port) +{ + DLMSTP_PACKET packet; + uint16_t pdu_len = mstp_port->DataLength; + unsigned i = 0; + + /* bounds check - maybe this should send an abort? */ + if (pdu_len > sizeof(packet.pdu)) + pdu_len = sizeof(packet.pdu); + if (pdu_len) { + MSTP_Packets++; + for (i = 0; i < Receive_Packet.pdu_len; i++) { + Receive_Packet.pdu[i] = mstp_port->InputBuffer[i]; + } + dlmstp_fill_bacnet_address(&Receive_Packet.address, + mstp_port->SourceAddress); + Receive_Packet.pdu_len = pdu_len; + Receive_Packet.ready = true; + } + + return pdu_len; +} + +/* for the MS/TP state machine to use for getting data to send */ +/* Return: amount of PDU data */ +uint16_t MSTP_Get_Send( + uint8_t src, /* source MS/TP address for creating packet */ + uint8_t * pdu, /* data to send */ + uint16_t max_pdu, /* amount of space available */ + unsigned timeout) /* milliseconds to wait for a packet */ +{ + uint16_t pdu_len = 0; /* return value */ + uint8_t destination = 0; /* destination address */ + + if (Transmit_Packet.ready) { + /* load destination MAC address */ + if (Transmit_Packet.address.mac_len == 1) { + destination = Transmit_Packet.address.mac[0]; + } else { + return 0; + } + if ((8 /* header len */ + Transmit_Packet.pdu_len) > MAX_MPDU) { + return 0; + } + /* convert the PDU into the MSTP Frame */ + pdu_len = MSTP_Create_Frame( + pdu, /* <-- loading this */ + max_pdu, + Transmit_Packet.frame_type, + destination, src, + &Transmit_Packet.pdu[0], + Transmit_Packet.pdu_len); + } + + return pdu_len; +} + +void dlmstp_set_mac_address(uint8_t mac_address) +{ + /* Master Nodes can only have address 0-127 */ + if (mac_address <= 127) { + MSTP_Port.This_Station = mac_address; + /* FIXME: implement your data storage */ + /* I2C_Write_Byte( + EEPROM_DEVICE_ADDRESS, + mac_address, + EEPROM_MSTP_MAC_ADDR); */ + if (mac_address > MSTP_Port.Nmax_master) + dlmstp_set_max_master(mac_address); + } + + return; +} + +uint8_t dlmstp_mac_address(void) +{ + return MSTP_Port.This_Station; +} + +/* This parameter represents the value of the Max_Info_Frames property of */ +/* the node's Device object. The value of Max_Info_Frames specifies the */ +/* maximum number of information frames the node may send before it must */ +/* pass the token. Max_Info_Frames may have different values on different */ +/* nodes. This may be used to allocate more or less of the available link */ +/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ +/* node, its value shall be 1. */ +void dlmstp_set_max_info_frames(uint8_t max_info_frames) +{ + if (max_info_frames >= 1) { + MSTP_Port.Nmax_info_frames = max_info_frames; + /* FIXME: implement your data storage */ + /* I2C_Write_Byte( + EEPROM_DEVICE_ADDRESS, + (uint8_t)max_info_frames, + EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */ + } + + return; +} + +uint8_t dlmstp_max_info_frames(void) +{ + return MSTP_Port.Nmax_info_frames; +} + +/* This parameter represents the value of the Max_Master property of the */ +/* node's Device object. The value of Max_Master specifies the highest */ +/* allowable address for master nodes. The value of Max_Master shall be */ +/* less than or equal to 127. If Max_Master is not writable in a node, */ +/* its value shall be 127. */ +void dlmstp_set_max_master(uint8_t max_master) +{ + if (max_master <= 127) { + if (MSTP_Port.This_Station <= max_master) { + MSTP_Port.Nmax_master = max_master; + /* FIXME: implement your data storage */ + /* I2C_Write_Byte( + EEPROM_DEVICE_ADDRESS, + max_master, + EEPROM_MSTP_MAX_MASTER_ADDR); */ + } + } + + return; +} + +uint8_t dlmstp_max_master(void) +{ + return MSTP_Port.Nmax_master; +} + +void dlmstp_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; /* counter */ + + my_address->mac_len = 1; + my_address->mac[0] = MSTP_Port.This_Station; + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void dlmstp_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 1; + dest->mac[0] = MSTP_BROADCAST_ADDRESS; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* always zero when DNET is broadcast */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} diff --git a/bacnet-stack/ports/atmega168/timer1.ods b/bacnet-stack/ports/atmega168/timer1.ods new file mode 100644 index 0000000000000000000000000000000000000000..5896796e77db27664af93a01595afed08ad1bedd GIT binary patch literal 21252 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 V203x@1pvUr&rhPM1E6!T^*;nST&Dm4 literal 0 HcmV?d00001