Commit 6941e0c5 authored by dingmz's avatar dingmz

merge sdk3.1.1 to master-panel.Z3Light12W

parents d204f7e5 3b9389c0
一:SDK3.1_V1.1 2021.09.16 SDKV3.1.1-20210916
SDK更新内容: 1、SDK中上报网关,不需要defaultResponds,而多控保留defaultResponds的功能;
1、SDK中上报网关,不需要defaultResponds,而多控保留defaultResponds的功能; 2、改编译脚本,修复频偏校准值未正常写入的问题;
2、改编译脚本,修复频偏校准值未正常写入的问题;
ikonke_app更新内容:
1、为了兼容老设备token的位置,防止OTA之后设备进入产测等状况发送,本次模板修改了自定义token和mfg-token的位置,将灯控面板新加入的标志位等后移,使新模板中的token和原本SDK中的token一一对应,并设置了四字节的SDK版本token,使其在OTA之后,单板和整机的标志位能够自动写入产测通过对应的值。
本次修改的具体实现为:在脚本中定义一个SDK-token的特殊值,并通过脚本有选择地在合成.hex文件时加入特殊值,生成.ota文件时不加入,当旧版本固件OTA到新固件时该token为非特殊值,在初始化的时候产测标志位会被自动写入产测通过的数值。
由于插座等固件中的token位置与灯控不相同,之后会加入新的token,目前token已加,功能在下一次更新时实现。
2、修改了cmei、isn等数组写入时,当数据长度为33字节时会复位的问题,但是目前该类型数组数据长度依旧定义为34字节。
3、增加了LED群组控制,将系统灯、字符灯和按键灯等分组并控制亮灭,opcode为ED17,如果有其他设备使用可以手动修改内容;增加了ED17的错误码E0。
4、删掉了interpan使能标志位与token相关内容。
5、删掉了已经不启用的installcode中与token相关的内容。
6、修复了FCC0中installcode因长度判断有误而无法读取属性的问题;
7、修复私有协议指令长度错误的问题
8、修复按下按键之后立刻读取心跳,数据粘包的问题
9、修改了token id,全部改为1000开始;
SDKV3.1.0-20210916
1、初始版本;
/***************************************************************************//** /***************************************************************************//**
* @file * @file
* @brief Routines for the On-Off plugin, which implements the On-Off server * @brief Routines for the On-Off plugin, which implements the On-Off server
* cluster. * cluster.
******************************************************************************* *******************************************************************************
* # License * # License
* <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b> * <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
******************************************************************************* *******************************************************************************
* *
* The licensor of this software is Silicon Laboratories Inc. Your use of this * The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License * software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at * Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This * www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the * software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code. * sections of the MSLA applicable to Source Code.
* *
******************************************************************************/ ******************************************************************************/
#include "af.h" #include "af.h"
#ifdef EMBER_AF_PLUGIN_REPORTING #ifdef EMBER_AF_PLUGIN_REPORTING
#include "app/framework/plugin/reporting/reporting.h" #include "app/framework/plugin/reporting/reporting.h"
#endif #endif
#ifdef EMBER_AF_PLUGIN_SCENES #ifdef EMBER_AF_PLUGIN_SCENES
#include "../scenes/scenes.h" #include "../scenes/scenes.h"
#endif //EMBER_AF_PLUGIN_SCENES #endif //EMBER_AF_PLUGIN_SCENES
#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER #ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER
#include "../zll-on-off-server/zll-on-off-server.h" #include "../zll-on-off-server/zll-on-off-server.h"
#endif #endif
#ifdef EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER #ifdef EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER
#include "../zll-level-control-server/zll-level-control-server.h" #include "../zll-level-control-server/zll-level-control-server.h"
#endif #endif
#ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE #ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE
static bool areStartUpOnOffServerAttributesTokenized(uint8_t endpoint); static bool areStartUpOnOffServerAttributesTokenized(uint8_t endpoint);
#endif #endif
EmberAfStatus emberAfOnOffClusterSetValueCallback(uint8_t endpoint, EmberAfStatus emberAfOnOffClusterSetValueCallback(uint8_t endpoint,
uint8_t command, uint8_t command,
bool initiatedByLevelChange) bool initiatedByLevelChange)
{ {
EmberAfStatus status; EmberAfStatus status;
bool currentValue, newValue; bool currentValue, newValue;
emberAfOnOffClusterPrintln("On/Off set value: %x %x", endpoint, command); emberAfOnOffClusterPrintln("On/Off set value: %x %x", endpoint, command);
// read current on/off value // read current on/off value
status = emberAfReadAttribute(endpoint, status = emberAfReadAttribute(endpoint,
ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_CLUSTER_ID,
ZCL_ON_OFF_ATTRIBUTE_ID, ZCL_ON_OFF_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, CLUSTER_MASK_SERVER,
(uint8_t *)&currentValue, (uint8_t *)&currentValue,
sizeof(currentValue), sizeof(currentValue),
NULL); // data type NULL); // data type
if (status != EMBER_ZCL_STATUS_SUCCESS) { if (status != EMBER_ZCL_STATUS_SUCCESS) {
emberAfOnOffClusterPrintln("ERR: reading on/off %x", status); emberAfOnOffClusterPrintln("ERR: reading on/off %x", status);
return status; return status;
} }
// if the value is already what we want to set it to then do nothing // if the value is already what we want to set it to then do nothing
if ((!currentValue && command == ZCL_OFF_COMMAND_ID) if ((!currentValue && command == ZCL_OFF_COMMAND_ID)
|| (currentValue && command == ZCL_ON_COMMAND_ID)) { || (currentValue && command == ZCL_ON_COMMAND_ID)) {
emberAfOnOffClusterPrintln("On/off already set to new value"); emberAfOnOffClusterPrintln("On/off already set to new value");
return EMBER_ZCL_STATUS_SUCCESS;
} extern uint8_t kZclClusterAttrDelayReportQueueAdd(uint8_t endpoint, uint16_t clusterId, uint16_t attributeId);
kZclClusterAttrDelayReportQueueAdd(endpoint, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID);
// we either got a toggle, or an on when off, or an off when on,
// so we need to swap the value return EMBER_ZCL_STATUS_SUCCESS;
newValue = !currentValue; }
emberAfOnOffClusterPrintln("Toggle on/off from %x to %x", currentValue, newValue);
// we either got a toggle, or an on when off, or an off when on,
// the sequence of updating on/off attribute and kick off level change effect should // so we need to swap the value
// be depend on whether we are turning on or off. If we are turning on the light, we newValue = !currentValue;
// should update the on/off attribute before kicking off level change, if we are emberAfOnOffClusterPrintln("Toggle on/off from %x to %x", currentValue, newValue);
// turning off the light, we should do the opposite, that is kick off level change
// before updating the on/off attribute. // the sequence of updating on/off attribute and kick off level change effect should
if (newValue) { // be depend on whether we are turning on or off. If we are turning on the light, we
// write the new on/off value // should update the on/off attribute before kicking off level change, if we are
status = emberAfWriteAttribute(endpoint, // turning off the light, we should do the opposite, that is kick off level change
ZCL_ON_OFF_CLUSTER_ID, // before updating the on/off attribute.
ZCL_ON_OFF_ATTRIBUTE_ID, if (newValue) {
CLUSTER_MASK_SERVER, // write the new on/off value
(uint8_t *)&newValue, status = emberAfWriteAttribute(endpoint,
ZCL_BOOLEAN_ATTRIBUTE_TYPE); ZCL_ON_OFF_CLUSTER_ID,
if (status != EMBER_ZCL_STATUS_SUCCESS) { ZCL_ON_OFF_ATTRIBUTE_ID,
emberAfOnOffClusterPrintln("ERR: writing on/off %x", status); CLUSTER_MASK_SERVER,
return status; (uint8_t *)&newValue,
} ZCL_BOOLEAN_ATTRIBUTE_TYPE);
if (status != EMBER_ZCL_STATUS_SUCCESS) {
// If initiatedByLevelChange is false, then we assume that the level change emberAfOnOffClusterPrintln("ERR: writing on/off %x", status);
// ZCL stuff has not happened and we do it here return status;
if (!initiatedByLevelChange }
&& emberAfContainsServer(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID)) {
emberAfOnOffClusterLevelControlEffectCallback(endpoint, // If initiatedByLevelChange is false, then we assume that the level change
newValue); // ZCL stuff has not happened and we do it here
} if (!initiatedByLevelChange
} else { && emberAfContainsServer(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID)) {
// If initiatedByLevelChange is false, then we assume that the level change emberAfOnOffClusterLevelControlEffectCallback(endpoint,
// ZCL stuff has not happened and we do it here newValue);
if (!initiatedByLevelChange }
&& emberAfContainsServer(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID)) { } else {
emberAfOnOffClusterLevelControlEffectCallback(endpoint, // If initiatedByLevelChange is false, then we assume that the level change
newValue); // ZCL stuff has not happened and we do it here
} if (!initiatedByLevelChange
&& emberAfContainsServer(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID)) {
// write the new on/off value emberAfOnOffClusterLevelControlEffectCallback(endpoint,
status = emberAfWriteAttribute(endpoint, newValue);
ZCL_ON_OFF_CLUSTER_ID, }
ZCL_ON_OFF_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, // write the new on/off value
(uint8_t *)&newValue, status = emberAfWriteAttribute(endpoint,
ZCL_BOOLEAN_ATTRIBUTE_TYPE); ZCL_ON_OFF_CLUSTER_ID,
if (status != EMBER_ZCL_STATUS_SUCCESS) { ZCL_ON_OFF_ATTRIBUTE_ID,
emberAfOnOffClusterPrintln("ERR: writing on/off %x", status); CLUSTER_MASK_SERVER,
return status; (uint8_t *)&newValue,
} ZCL_BOOLEAN_ATTRIBUTE_TYPE);
} if (status != EMBER_ZCL_STATUS_SUCCESS) {
emberAfOnOffClusterPrintln("ERR: writing on/off %x", status);
#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER return status;
if (initiatedByLevelChange) { }
emberAfPluginZllOnOffServerLevelControlZllExtensions(endpoint); }
}
#endif #ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER
if (initiatedByLevelChange) {
// the scene has been changed (the value of on/off has changed) so emberAfPluginZllOnOffServerLevelControlZllExtensions(endpoint);
// the current scene as descibed in the attribute table is invalid, }
// so mark it as invalid (just writes the valid/invalid attribute) #endif
if (emberAfContainsServer(endpoint, ZCL_SCENES_CLUSTER_ID)) {
emberAfScenesClusterMakeInvalidCallback(endpoint); // the scene has been changed (the value of on/off has changed) so
} // the current scene as descibed in the attribute table is invalid,
// so mark it as invalid (just writes the valid/invalid attribute)
// The returned status is based solely on the On/Off cluster. Errors in the if (emberAfContainsServer(endpoint, ZCL_SCENES_CLUSTER_ID)) {
// Level Control and/or Scenes cluster are ignored. emberAfScenesClusterMakeInvalidCallback(endpoint);
return EMBER_ZCL_STATUS_SUCCESS; }
}
// The returned status is based solely on the On/Off cluster. Errors in the
bool emberAfOnOffClusterOffCallback(void) // Level Control and/or Scenes cluster are ignored.
{ return EMBER_ZCL_STATUS_SUCCESS;
EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(), }
ZCL_OFF_COMMAND_ID,
false); bool emberAfOnOffClusterOffCallback(void)
#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER {
if (status == EMBER_ZCL_STATUS_SUCCESS) { EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(),
emberAfPluginZllOnOffServerOffZllExtensions(emberAfCurrentCommand()); ZCL_OFF_COMMAND_ID,
} false);
#endif #ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER
emberAfSendImmediateDefaultResponse(status); if (status == EMBER_ZCL_STATUS_SUCCESS) {
return true; emberAfPluginZllOnOffServerOffZllExtensions(emberAfCurrentCommand());
} }
#endif
bool emberAfOnOffClusterOnCallback(void) emberAfSendImmediateDefaultResponse(status);
{ return true;
EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(), }
ZCL_ON_COMMAND_ID,
false); bool emberAfOnOffClusterOnCallback(void)
#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER {
if (status == EMBER_ZCL_STATUS_SUCCESS) { EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(),
emberAfPluginZllOnOffServerOnZllExtensions(emberAfCurrentCommand()); ZCL_ON_COMMAND_ID,
} false);
#endif #ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER
emberAfSendImmediateDefaultResponse(status); if (status == EMBER_ZCL_STATUS_SUCCESS) {
return true; emberAfPluginZllOnOffServerOnZllExtensions(emberAfCurrentCommand());
} }
#endif
bool emberAfOnOffClusterToggleCallback(void) emberAfSendImmediateDefaultResponse(status);
{ return true;
EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(), }
ZCL_TOGGLE_COMMAND_ID,
false); bool emberAfOnOffClusterToggleCallback(void)
#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER {
if (status == EMBER_ZCL_STATUS_SUCCESS) { EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(),
emberAfPluginZllOnOffServerToggleZllExtensions(emberAfCurrentCommand()); ZCL_TOGGLE_COMMAND_ID,
} false);
#endif #ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER
emberAfSendImmediateDefaultResponse(status); if (status == EMBER_ZCL_STATUS_SUCCESS) {
return true; emberAfPluginZllOnOffServerToggleZllExtensions(emberAfCurrentCommand());
} }
#endif
void emberAfOnOffClusterServerInitCallback(uint8_t endpoint) emberAfSendImmediateDefaultResponse(status);
{ return true;
#ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE }
// StartUp behavior relies on OnOff and StartUpOnOff attributes being tokenized.
if (areStartUpOnOffServerAttributesTokenized(endpoint)) { void emberAfOnOffClusterServerInitCallback(uint8_t endpoint)
// Read the StartUpOnOff attribute and set the OnOff attribute as per {
// following from zcl 7 14-0127-20i-zcl-ch-3-general.doc. #ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE
// 3.8.2.2.5 StartUpOnOff Attribute // StartUp behavior relies on OnOff and StartUpOnOff attributes being tokenized.
// The StartUpOnOff attribute SHALL define the desired startup behavior of a if (areStartUpOnOffServerAttributesTokenized(endpoint)) {
// lamp device when it is supplied with power and this state SHALL be // Read the StartUpOnOff attribute and set the OnOff attribute as per
// reflected in the OnOff attribute. The values of the StartUpOnOff // following from zcl 7 14-0127-20i-zcl-ch-3-general.doc.
// attribute are listed below. // 3.8.2.2.5 StartUpOnOff Attribute
// Table 3 46. Values of the StartUpOnOff Attribute // The StartUpOnOff attribute SHALL define the desired startup behavior of a
// Value Action on power up // lamp device when it is supplied with power and this state SHALL be
// 0x00 Set the OnOff attribute to 0 (off). // reflected in the OnOff attribute. The values of the StartUpOnOff
// 0x01 Set the OnOff attribute to 1 (on). // attribute are listed below.
// 0x02 If the previous value of the OnOff attribute is equal to 0, // Table 3 46. Values of the StartUpOnOff Attribute
// set the OnOff attribute to 1.If the previous value of the OnOff // Value Action on power up
// attribute is equal to 1, set the OnOff attribute to 0 (toggle). // 0x00 Set the OnOff attribute to 0 (off).
// 0x03-0xfe These values are reserved. No action. // 0x01 Set the OnOff attribute to 1 (on).
// 0xff Set the OnOff attribute to its previous value. // 0x02 If the previous value of the OnOff attribute is equal to 0,
// set the OnOff attribute to 1.If the previous value of the OnOff
// Initialize startUpOnOff to No action value 0xFE // attribute is equal to 1, set the OnOff attribute to 0 (toggle).
uint8_t startUpOnOff = 0xFE; // 0x03-0xfe These values are reserved. No action.
EmberAfStatus status = emberAfReadAttribute(endpoint, // 0xff Set the OnOff attribute to its previous value.
ZCL_ON_OFF_CLUSTER_ID,
ZCL_START_UP_ON_OFF_ATTRIBUTE_ID, // Initialize startUpOnOff to No action value 0xFE
CLUSTER_MASK_SERVER, uint8_t startUpOnOff = 0xFE;
(uint8_t *)&startUpOnOff, EmberAfStatus status = emberAfReadAttribute(endpoint,
sizeof(startUpOnOff), ZCL_ON_OFF_CLUSTER_ID,
NULL); ZCL_START_UP_ON_OFF_ATTRIBUTE_ID,
if (status == EMBER_ZCL_STATUS_SUCCESS) { CLUSTER_MASK_SERVER,
// Initialise updated value to 0 (uint8_t *)&startUpOnOff,
bool updatedOnOff = 0; sizeof(startUpOnOff),
status = emberAfReadAttribute(endpoint, NULL);
ZCL_ON_OFF_CLUSTER_ID, if (status == EMBER_ZCL_STATUS_SUCCESS) {
ZCL_ON_OFF_ATTRIBUTE_ID, // Initialise updated value to 0
CLUSTER_MASK_SERVER, bool updatedOnOff = 0;
(uint8_t *)&updatedOnOff, status = emberAfReadAttribute(endpoint,
sizeof(updatedOnOff), ZCL_ON_OFF_CLUSTER_ID,
NULL); ZCL_ON_OFF_ATTRIBUTE_ID,
if (status == EMBER_ZCL_STATUS_SUCCESS) { CLUSTER_MASK_SERVER,
switch (startUpOnOff) { (uint8_t *)&updatedOnOff,
case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_OFF: sizeof(updatedOnOff),
updatedOnOff = 0; // Off NULL);
break; if (status == EMBER_ZCL_STATUS_SUCCESS) {
case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_ON: switch (startUpOnOff) {
updatedOnOff = 1; //On case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_OFF:
break; updatedOnOff = 0; // Off
case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_TOGGLE: break;
updatedOnOff = !updatedOnOff; case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_ON:
break; updatedOnOff = 1; //On
case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_PREVIOUS: break;
default: case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_TOGGLE:
// All other values 0x03- 0xFE are reserved - no action. updatedOnOff = !updatedOnOff;
// When value is 0xFF - update with last value - that is as good as break;
// no action. case EMBER_ZCL_START_UP_ON_OFF_VALUE_SET_TO_PREVIOUS:
break; default:
} // All other values 0x03- 0xFE are reserved - no action.
status = emberAfWriteAttribute(endpoint, // When value is 0xFF - update with last value - that is as good as
ZCL_ON_OFF_CLUSTER_ID, // no action.
ZCL_ON_OFF_ATTRIBUTE_ID, break;
CLUSTER_MASK_SERVER, }
(uint8_t *)&updatedOnOff, status = emberAfWriteAttribute(endpoint,
ZCL_BOOLEAN_ATTRIBUTE_TYPE); ZCL_ON_OFF_CLUSTER_ID,
} ZCL_ON_OFF_ATTRIBUTE_ID,
} CLUSTER_MASK_SERVER,
} (uint8_t *)&updatedOnOff,
#endif ZCL_BOOLEAN_ATTRIBUTE_TYPE);
emberAfPluginOnOffClusterServerPostInitCallback(endpoint); }
} }
}
#ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE #endif
static bool areStartUpOnOffServerAttributesTokenized(uint8_t endpoint) emberAfPluginOnOffClusterServerPostInitCallback(endpoint);
{ }
EmberAfAttributeMetadata *metadata;
#ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE
metadata = emberAfLocateAttributeMetadata(endpoint, static bool areStartUpOnOffServerAttributesTokenized(uint8_t endpoint)
ZCL_ON_OFF_CLUSTER_ID, {
ZCL_ON_OFF_ATTRIBUTE_ID, EmberAfAttributeMetadata *metadata;
CLUSTER_MASK_SERVER,
EMBER_AF_NULL_MANUFACTURER_CODE); metadata = emberAfLocateAttributeMetadata(endpoint,
if (!emberAfAttributeIsTokenized(metadata)) { ZCL_ON_OFF_CLUSTER_ID,
return false; ZCL_ON_OFF_ATTRIBUTE_ID,
} CLUSTER_MASK_SERVER,
EMBER_AF_NULL_MANUFACTURER_CODE);
metadata = emberAfLocateAttributeMetadata(endpoint, if (!emberAfAttributeIsTokenized(metadata)) {
ZCL_ON_OFF_CLUSTER_ID, return false;
ZCL_START_UP_ON_OFF_ATTRIBUTE_ID, }
CLUSTER_MASK_SERVER,
EMBER_AF_NULL_MANUFACTURER_CODE); metadata = emberAfLocateAttributeMetadata(endpoint,
if (!emberAfAttributeIsTokenized(metadata)) { ZCL_ON_OFF_CLUSTER_ID,
return false; ZCL_START_UP_ON_OFF_ATTRIBUTE_ID,
} CLUSTER_MASK_SERVER,
EMBER_AF_NULL_MANUFACTURER_CODE);
return true; if (!emberAfAttributeIsTokenized(metadata)) {
} return false;
#endif }
return true;
}
#endif
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment