Exemple #1
0
/**@brief Handle events from m_pwr_mgmt_timer.
 */
static void nrf_pwr_mgmt_timeout_handler(void * p_context)
{
#if NRF_PWR_MGMT_CONFIG_CPU_USAGE_MONITOR_ENABLED
    uint32_t delta;
    uint32_t ticks;
    uint8_t  cpu_usage;

    ticks = app_timer_cnt_get();
    UNUSED_VARIABLE(app_timer_cnt_diff_compute(ticks, m_ticks_last, &delta));
    cpu_usage = 100 * (delta - m_ticks_sleeping) / delta;

    NRF_LOG_DEBUG("CPU Usage: %d%%\r\n", cpu_usage);
    if (m_max_cpu_usage < cpu_usage)
    {
        m_max_cpu_usage = cpu_usage;
    }

    m_ticks_last        = ticks;
    m_ticks_sleeping    = 0;
#endif // NRF_PWR_MGMT_CONFIG_CPU_USAGE_MONITOR_ENABLED

#if NRF_PWR_MGMT_CONFIG_AUTO_SHUTDOWN_RETRY
    if (m_sysoff_guard)
    {
        // Try to continue the shutdown procedure.
        nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_CONTINUE);
        return;
    }
#endif // NRF_PWR_MGMT_CONFIG_AUTO_SHUTDOWN_RETRY

#if NRF_PWR_MGMT_CONFIG_STANDBY_TIMEOUT_ENABLED
    if (m_standby_counter < NRF_PWR_MGMT_CONFIG_STANDBY_TIMEOUT_S)
    {
        m_standby_counter++;
    }
    else
    {
        nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
    }
#endif // NRF_PWR_MGMT_CONFIG_STANDBY_TIMEOUT_ENABLED
}
Exemple #2
0
void Node::TimerTickHandler(u16 timerMs)
{
	passsedTimeSinceLastTimerHandler = timerMs;

	appTimerMs += timerMs;
	currentStateTimeoutMs -= timerMs;

	//Update our global time
	//TODO: should take care of PRESCALER register value as well
	u32 rtc1, passedTime;
	app_timer_cnt_get(&rtc1);
	app_timer_cnt_diff_compute(rtc1, globalTimeSetAt, &passedTime);

	globalTime += passedTime;
	app_timer_cnt_get(&globalTimeSetAt); //Update the time that the timestamp was last updated

	//FIXME: Used for debugging invalid state errors, reboot the nodes until an error occurs
	if (lookingForInvalidStateErrors)
	{
		if (appTimerMs > 25000)
		{
			NVIC_SystemReset();
		}
	}

	//logt("TIMER", "Tick, appTimer %d, stateTimeout:%d, lastDecisionTime:%d, state:%d=>%d", appTimerMs, currentStateTimeoutMs, lastDecisionTimeMs, currentDiscoveryState, nextDiscoveryState);

	//Check if we should switch states because of timeouts
	if (nextDiscoveryState != INVALID_STATE && currentStateTimeoutMs <= 0)
	{

		//Go to the next state
		ChangeState(nextDiscoveryState);
	}

	//FIXME: there should be a handshake timeout

	if (currentLedMode == LED_MODE_CONNECTIONS)
	{
		//Now we test for blinking lights
		u8 countHandshake = (cm->inConnection->handshakeDone ? 1 : 0) + (cm->outConnections[0]->handshakeDone ? 1 : 0) + (cm->outConnections[1]->handshakeDone ? 1 : 0) + (cm->outConnections[2]->handshakeDone ? 1 : 0);
		u8 countConnected = (cm->inConnection->isConnected ? 1 : 0) + (cm->outConnections[0]->isConnected ? 1 : 0) + (cm->outConnections[1]->isConnected ? 1 : 0) + (cm->outConnections[2]->isConnected ? 1 : 0);

		//Check if we want to switch one off
		if (appTimerMs - LedRed->lastStateChangeMs >= 300) LedRed->Off();
		if (appTimerMs - LedGreen->lastStateChangeMs >= 300) LedGreen->Off();
		if (appTimerMs - LedBlue->lastStateChangeMs >= 300) LedBlue->Off();

		if (appTimerMs % 2000 == 0 || appTimerMs % 2000 == 500 || appTimerMs % 2000 == 1000 || appTimerMs % 2000 == 1500)
		{
			u8 id = (appTimerMs % 2000) / 500;

			if (countHandshake == 0 && countConnected == 0)
			{
				LedRed->On();
				LedRed->lastStateChangeMs = appTimerMs;
			}
			if (countConnected > id)
			{
				LedGreen->On();
				LedGreen->lastStateChangeMs = appTimerMs;
			}
			if (countHandshake > id)
			{
				LedBlue->On();
				LedBlue->lastStateChangeMs = appTimerMs;
			}
		}
	}
}
Exemple #3
0
bool Node::TerminalCommandHandler(string commandName, vector<string> commandArgs)
{
	/************* SYSTEM ***************/
	if (commandName == "reset")
	{
		sd_nvic_SystemReset();
	}
	else if (commandName == "startterm")
	{
		Terminal::promptAndEchoMode = true;
	}
	else if (commandName == "stopterm")
	{
		Terminal::promptAndEchoMode = false;
		Logger::getInstance().disableAll();
	}
	/************* NODE ***************/
	else if (commandName == "status")
	{
		PrintStatus();
	}
	else if (commandName == "bufferstat")
	{
		PrintBufferStatus();
	}
	else if (commandName == "stat")
	{
		PrintSingleLineStatus();
	}

	else if (commandName == "data")
	{
		nodeID receiverId = 0;
		if(commandArgs.size() > 0){
			if(commandArgs[0] == "sink") receiverId = NODE_ID_SHORTEST_SINK;
			else if(commandArgs[0] == "hop") receiverId = NODE_ID_HOPS_BASE + 1;
			else receiverId = NODE_ID_BROADCAST;
		}

		//Send data over all connections
		connPacketData1 data;
		data.header.messageType = MESSAGE_TYPE_DATA_1;
		data.header.sender = persistentConfig.nodeId;
		data.header.receiver = receiverId;

		data.payload.length = 7;
		data.payload.data[0] = 1;
		data.payload.data[1] = 3;
		data.payload.data[2] = 3;

		bool reliable = (commandArgs.size() == 0) ? false : true;

		cm->SendMessageToReceiver(NULL, (u8*) &data, SIZEOF_CONN_PACKET_DATA_1, reliable);
	}
	else if(commandName == "datal")
	{
		//Send some large data that is split over messages
		const u8 dataLength = 45;
		u8 _packet[dataLength];
		connPacketHeader* packet = (connPacketHeader*)_packet;
		packet->messageType = MESSAGE_TYPE_DATA_1;
		packet->receiver = 0;
		packet->sender = persistentConfig.nodeId;

		for(u32 i=0; i< dataLength-5; i++){
			_packet[i+5] = i+1;
		}

		cm->SendMessageToReceiver(NULL, _packet, dataLength, true);


	}
	else if (commandName == "loss")
	{
		//Simulate connection loss
		this->persistentConfig.connectionLossCounter++;
		clusterId = this->GenerateClusterID();
		this->UpdateJoinMePacket(NULL);

	}
	else if (commandName == "settime")
	{
		u64 timeStamp = (atoi(commandArgs[0].c_str()) * (u64)APP_TIMER_CLOCK_FREQ);

		//Set the time for our node
		globalTime = timeStamp;
		app_timer_cnt_get(&globalTimeSetAt);
	}
	else if(commandName == "gettime")
	{
		u32 rtc1;
		app_timer_cnt_get(&rtc1);

		char timestring[50];
		Logger::getInstance().convertTimestampToString(globalTime, timestring);

		trace("Time is currently %s, setAt:%d, rtc1:%u" EOL, timestring, globalTimeSetAt, rtc1);
	}
	else if (commandName == "sendtime")
	{
		//Generate a timestamp packet and send it to all other nodes
		connPacketUpdateTimestamp packet;

		packet.header.messageType = MESSAGE_TYPE_UPDATE_TIMESTAMP;
		packet.header.sender = persistentConfig.nodeId;
		packet.header.receiver = 0;

		//Data must not be filled because it is set in the fillTransmitBuffers method
		//Because it might still take some time from filling the buffer to sending the packet
		//We should use the radio event or estimate the sending based on the connetion parameters

		//It is then received and processed in the Connectionmanager::messageReceivedCallback

		cm->SendMessageToReceiver(NULL, (u8*)&packet, SIZEOF_CONN_PACKET_UPDATE_TIMESTAMP, true);

	}
	else if (commandName == "discovery")
	{
		if (commandArgs.size() < 1 || commandArgs[0] == "high")
		{
			noNodesFoundCounter = 0;
			ChangeState(discoveryState::DISCOVERY_HIGH);
		}
		else if (commandArgs[0] == "low")
		{
			noNodesFoundCounter = 50;
			ChangeState(discoveryState::DISCOVERY_LOW);

		}
		else if (commandArgs[0] == "off")
		{
			ChangeState(discoveryState::DISCOVERY_OFF);
		}
	}
	else if (commandName == "discover")
	{
		ChangeState(discoveryState::DISCOVERY_HIGH);
	}
	else if (commandName == "discoverylow")
	{
		noNodesFoundCounter = 50;

	}
	//Trigger this to save the current node configuration
	else if (commandName == "savenode")
	{
		persistentConfig.reserved++;
		Storage::getInstance().QueuedWrite((u8*) &persistentConfig, sizeof(NodeConfiguration), 0, this);

	}
	else if (commandName == "stop")
	{
		DisableStateMachine(true);
	}
	else if (commandName == "start")
	{
		DisableStateMachine(false);
	}
	else if (commandName == "break")
	{
		Config->breakpointToggleActive = !Config->breakpointToggleActive;
	}
	else if (commandName == "connect")
	{

		for (int i = 0; i <NUM_TEST_DEVICES ; i++)
		{
			if (strcmp(commandArgs[0].c_str(), testDevices[i].name) == 0)
			{
				trace("Trying to connecting to node %s", testDevices[i].name);

				cm->ConnectAsMaster(testDevices[i].id, &testDevices[i].addr, 14);

			}
		}

	}
	else if (commandName == "disconnect")
	{
		if (commandArgs.size() > 0)
		{
			u8 connectionNumber = atoi(commandArgs[0].c_str());

			cm->connections[connectionNumber]->Disconnect();
		}
	}

	else if (commandName == "heap")
	{

		Utility::CheckFreeHeap();

		return true;

	}
	else if (commandName == "modules")
	{

		for(u32 i=0; i<MAX_MODULE_COUNT; i++)
		{
			if(activeModules[i] != NULL) log("Module: %s", activeModules[i]->moduleName);
		}

		return true;

	}
	else if (commandName == "yousink")
	{

		this->persistentConfig.deviceType = deviceTypes::DEVICE_TYPE_SINK;

		return true;

	}
	else if (commandName == "set_nodeid")
	{

		this->persistentConfig.nodeId = atoi(commandArgs[0].c_str());

		return true;

	}

	/************* UART COMMANDS ***************/
	else if (commandName == "uart_set_campaign")
	{
		if (commandArgs.size() > 0){
			AdvertisingController::SetScanResponseData(this, commandArgs[0]);
		} else {
			uart_error(Logger::ARGUMENTS_WRONG);
		}
	}
	else
	{
		return false;
	}
	return true;
}
Exemple #4
0
/*
 * Get the elapsed time since startup in milliseconds
 */
uint64_t get_current_time_millis(void) {
    return (app_timer_cnt_get() / APP_TIMER_CLOCK_FREQ) +
           (HEARTBEAT_TIMER_TIMEOUT * heartbeat_timeout_count);
}
void ConnectionManager::fillTransmitBuffers(){

	u32 err;

	//Fill with unreliable packets
	for(int i=0; i<Config->meshMaxConnections; i++)
	{
		while( connections[i]->isConnected() && connections[i]->GetPendingPackets() > 0)
		{
			bool packetCouldNotBeSent = false;

			bool reliable = false;
			u8* data = NULL;
			u16 dataSize = 0;

			if(!connections[i]->handshakeDone() && connections[i]->packetSendQueue->_numElements < 1){
				//TODO: might want to use a different method then GetPendingPackets if we are not in the handshake
				break;
			}

			//A cluster info update is waiting, this is important to send first (but only if handshake is finished and if no split packet is eing sent)
			if(
					connections[i]->handshakeDone()
					&& connections[i]->currentClusterInfoUpdatePacket.header.messageType != 0
					&& connections[i]->packetSendPosition == 0
			){
				//If a clusterUpdate is available we send it immediately
				reliable = true;
				data = (u8*)&(connections[i]->currentClusterInfoUpdatePacket);
				dataSize = SIZEOF_CONN_PACKET_CLUSTER_INFO_UPDATE;

				logt("CM", "Filling CLUSTER UPDATE for CONN %u", connections[i]->connectionId);
			}
			//Pick the next packet from the packet queue
			else
			{
				//Get one packet from the packet queue
				sizedData packet = connections[i]->packetSendQueue->PeekNext();
				if(packet.length > 0){
					reliable = packet.data[0];
					data = packet.data + 1;
					dataSize = packet.length - 1;
				} else {
					break;
				}
			}

			//Multi-part messages are only supported reliable
			//Switch packet to reliable if it is a multipart packet
			if(dataSize > MAX_DATA_SIZE_PER_WRITE) reliable = true;
			else ((connPacketHeader*) data)->hasMoreParts = 0;

			//The Next packet should be sent reliably
			if(reliable){
				if(connections[i]->reliableBuffersFree > 0){
					//Check if the packet can be transmitted in one MTU
					//If not, it will be sent with message splitting and reliable
					if(dataSize > MAX_DATA_SIZE_PER_WRITE){

						//We might already have started to transmit the packet
						if(connections[i]->packetSendPosition != 0){
							//we need to modify the data a little and build our split
							//message header. This does overwrite some of the old data
							//but that's already been transmitted
							connPacketSplitHeader* newHeader = (connPacketSplitHeader*)(data + connections[i]->packetSendPosition);
							newHeader->hasMoreParts = (dataSize - connections[i]->packetSendPosition > MAX_DATA_SIZE_PER_WRITE) ? 1: 0;
							newHeader->messageType = ((connPacketHeader*) data)->messageType; //We take it from the start of our packet which should still be intact

							//If the packet has more parts, we send a full packet, otherwise we send the remaining bits
							if(newHeader->hasMoreParts) dataSize = MAX_DATA_SIZE_PER_WRITE;
							else dataSize = dataSize - connections[i]->packetSendPosition;

							//Now we set the data pointer to where we left the last time minus our new header
							data = (u8*)newHeader;

						}
						//Or maybe this is the start of the transmission
						else
						{
							((connPacketHeader*) data)->hasMoreParts = 1;
							//Data is alright, but dataSize must be set to its new value
							dataSize = MAX_DATA_SIZE_PER_WRITE;
						}
					}


					//Update packet timestamp as close as possible before sending it
					//TODO: This could be done more accurate because we receive an event when the
					//Packet was sent, so we could calculate the time between sending and getting the event
					//And send a second packet with the time difference.
					if(((connPacketHeader*) data)->messageType == MESSAGE_TYPE_UPDATE_TIMESTAMP){
						//Add the time that it took from setting the time until it gets send
						u32 additionalTime;
						u32 rtc1;
						app_timer_cnt_get(&rtc1);
						app_timer_cnt_diff_compute(rtc1, node->globalTimeSetAt, &additionalTime);

						logt("NODE", "sending time:%u with prevRtc1:%u, rtc1:%u, diff:%u", (u32)node->globalTime, node->globalTimeSetAt, rtc1, additionalTime);

						((connPacketUpdateTimestamp*) data)->timestamp = node->globalTime + additionalTime;


						/*((connPacketUpdateTimestamp*) data)->timestamp = 0;
						((connPacketUpdateTimestamp*) data)->milliseconds = 0;*/
					}


					//Finally, send the packet to the SoftDevice
					err = GATTController::bleWriteCharacteristic(connections[i]->connectionHandle, connections[i]->writeCharacteristicHandle, data, dataSize, true);

					if(err != NRF_SUCCESS) logt("ERROR", "GATT WRITE ERROR %u", err);

					if(err == NRF_SUCCESS){
						//Consume a buffer because the packet was sent
						connections[i]->reliableBuffersFree--;

						memcpy(connections[i]->lastSentPacket, data, dataSize);

						//A special packet that is not from the packetqueue is emptied as soon as it is in the send buffer
						//If it can not be sent, the connection will be disconnected because of a timeout, so we must not await
						//A success event
						if(((connPacketHeader*)data)->messageType == MESSAGE_TYPE_CLUSTER_INFO_UPDATE){
							connections[i]->ClusterUpdateSentHandler();
						} else {

						}

					} else if(err == NRF_ERROR_DATA_SIZE || err == NRF_ERROR_INVALID_PARAM){
						logt("ERROR", "NRF_ERROR sending %u!!!!!!!!!!!!!", err);
						//Drop the packet if it's faulty
						if(((connPacketHeader*)data)->messageType == MESSAGE_TYPE_CLUSTER_INFO_UPDATE){
							logt("ERROR", "MALFORMED CLUSTER_INFO_UPDATE packet");
						} else {
							logt("ERROR", "MALFORMED DATA PACKET FROM QUEUE");
							connections[i]->packetSendQueue->DiscardNext();
						}
					} else {
						//Will try to send it later
						packetCouldNotBeSent = true;
					}

				} else {
					packetCouldNotBeSent = true;
				}
			}

			//The next packet is to be sent unreliably
			if(!reliable){
				if(connections[i]->unreliableBuffersFree > 0)
				{
					err = GATTController::bleWriteCharacteristic(connections[i]->connectionHandle, connections[i]->writeCharacteristicHandle, data, dataSize, false);

					if(err != NRF_SUCCESS) logt("ERROR", "GATT WRITE ERROR %u", err);

					if(err == NRF_SUCCESS){
						//Consumes a send Buffer
						connections[i]->unreliableBuffersFree--;
						logt("CONN", "packet to conn %u (txfree: %d)", i, connections[i]->unreliableBuffersFree);

						memcpy(connections[i]->lastSentPacket, data, dataSize);
					}

					//In either case (success or faulty packet) we drop the packet
					if(err == NRF_SUCCESS || err == NRF_ERROR_DATA_SIZE || err == NRF_ERROR_INVALID_PARAM){

						connections[i]->packetSendQueue->DiscardNext();

					} else {
						//Will try to send it later
						packetCouldNotBeSent = true;
					}

				} else {
					packetCouldNotBeSent = true;
				}
			}

			//Go to next connection if a packet (either reliable or unreliable)
			//could not be sent because the corresponding buffers are full
			if(packetCouldNotBeSent) break;
		}
	}
}
void ConnectionManager::messageReceivedCallback(ble_evt_t* bleEvent)
{
	ConnectionManager* cm = ConnectionManager::getInstance();

	//Handles BLE_GATTS_EVT_WRITE


	//FIXME: must check for reassembly buffer size, if it is bigger, a stack overflow will occur

	Connection* connection = cm->GetConnectionFromHandle(bleEvent->evt.gatts_evt.conn_handle);
	if (connection != NULL)
	{
		//TODO: At this point we should check if the write was a valid operation for the mesh
		//Invalid actions should cause a disconnect
		/*if( bleEvent->evt.gatts_evt.params.write.handle != GATTController::getMeshWriteHandle() ){
			connection->Disconnect();
			logt("ERROR", "Non mesh device was disconnected");
		}*/


		connPacketHeader* packet = (connPacketHeader*)bleEvent->evt.gatts_evt.params.write.data;

		//At first, some special treatment for out timestamp packet
		if(packet->messageType == MESSAGE_TYPE_UPDATE_TIMESTAMP)
		{
			//Set our time to the received timestamp and update the time when we've received this packet
			app_timer_cnt_get(&cm->node->globalTimeSetAt);
			cm->node->globalTime = ((connPacketUpdateTimestamp*)packet)->timestamp;

			logt("NODE", "time updated at:%u with timestamp:%u", cm->node->globalTimeSetAt, (u32)cm->node->globalTime);
		}

		u8 t = ((connPacketHeader*)bleEvent->evt.gatts_evt.params.write.data)->messageType;

		if( t != 20 && t != 21 && t != 22 && t != 23 && t != 30 && t != 31 && t != 50 && t != 51 && t != 52 && t != 53 && t != 56 && t != 57 && t != 60 && t != 61 && t != 62 && t != 80 && t != 81){
			logt("ERROR", "WAAAAAAAAAAAAAHHHHH, WRONG DATAAAAAAAAAAAAAAAAA!!!!!!!!!");
		}

		//Print packet as hex
		char stringBuffer[100];
		Logger::getInstance().convertBufferToHexString(bleEvent->evt.gatts_evt.params.write.data, bleEvent->evt.gatts_evt.params.write.len, stringBuffer, 100);
		logt("CONN_DATA", "Received type %d, hasMore %d, length %d, reliable %d:", ((connPacketHeader*)bleEvent->evt.gatts_evt.params.write.data)->messageType, ((connPacketHeader*)bleEvent->evt.gatts_evt.params.write.data)->hasMoreParts, bleEvent->evt.gatts_evt.params.write.len, bleEvent->evt.gatts_evt.params.write.op);
		logt("CONN_DATA", "%s", stringBuffer);

		//Check if we need to reassemble the packet
		if(connection->packetReassemblyPosition == 0 && packet->hasMoreParts == 0)
		{

			//Single packet, no more data
			connectionPacket p;
			p.connectionHandle = bleEvent->evt.gatts_evt.conn_handle;
			p.data = bleEvent->evt.gatts_evt.params.write.data;
			p.dataLength = bleEvent->evt.gatts_evt.params.write.len;
			p.reliable = bleEvent->evt.gatts_evt.params.write.op == BLE_GATTS_OP_WRITE_CMD ? false : true;

			connection->ReceivePacketHandler(&p);

		}
		//First of a multipart packet, still has more parts
		else if(connection->packetReassemblyPosition == 0 && packet->hasMoreParts)
		{
			//Save at correct position of
			memcpy(
					connection->packetReassemblyBuffer,
					bleEvent->evt.gatts_evt.params.write.data,
					bleEvent->evt.gatts_evt.params.write.len
				);

			connection->packetReassemblyPosition += bleEvent->evt.gatts_evt.params.write.len;

			//Do not notify anyone until packet is finished
			logt("CM", "Received first part of message");
		}
		//Multipart packet, intermediate or last frame
		else if(connection->packetReassemblyPosition != 0)
		{
			memcpy(
				connection->packetReassemblyBuffer + connection->packetReassemblyPosition,
				bleEvent->evt.gatts_evt.params.write.data + SIZEOF_CONN_PACKET_SPLIT_HEADER,
				bleEvent->evt.gatts_evt.params.write.len - SIZEOF_CONN_PACKET_SPLIT_HEADER
			);

			//Intermediate packet
			if(packet->hasMoreParts){
				connection->packetReassemblyPosition += bleEvent->evt.gatts_evt.params.write.len - SIZEOF_CONN_PACKET_SPLIT_HEADER;

				logt("CM", "Received middle part of message");

			//Final packet
			} else {
				logt("CM", "Received last part of message");

				//Notify connection
				connectionPacket p;
				p.connectionHandle = bleEvent->evt.gatts_evt.conn_handle;
				p.data = connection->packetReassemblyBuffer;
				p.dataLength = bleEvent->evt.gatts_evt.params.write.len + connection->packetReassemblyPosition - SIZEOF_CONN_PACKET_SPLIT_HEADER;
				p.reliable = bleEvent->evt.gatts_evt.params.write.op == BLE_GATTS_OP_WRITE_CMD ? false : true;

				//Reset the assembly buffer
				connection->packetReassemblyPosition = 0;

				connection->ReceivePacketHandler(&p);
			}
		}
	}
}
void scr_mngr_draw_screen(void) {
		scr_mngr_draw_ctx draw_ctx;
		draw_ctx.force_colors = false;
	
	  if (scr_alert_notification_state != SCR_ALERT_NOTIFICATION_STATE_NONE) {
				if (scr_alert_notification_state == SCR_ALERT_NOTIFICATION_STATE_INIT) {
						scr_mngr_handle_event_internal(SCR_ALERT_NOTIFICATION, SCR_EVENT_INIT_SCREEN, scr_alert_notification_address);
					  // draw alert notification screen
						mlcd_fb_clear();
						scr_mngr_handle_event_internal(SCR_ALERT_NOTIFICATION, SCR_EVENT_DRAW_SCREEN, (uint32_t)&draw_ctx);
						scr_alert_notification_state = SCR_ALERT_NOTIFICATION_STATE_SHOW;
				} else if (scr_alert_notification_state == SCR_ALERT_NOTIFICATION_STATE_SHOW) {
						scr_mngr_handle_event_internal(SCR_ALERT_NOTIFICATION, SCR_EVENT_REFRESH_SCREEN, (uint32_t)&draw_ctx);
				} else if (scr_alert_notification_state == SCR_ALERT_NOTIFICATION_STATE_CLOSE) {
						scr_mngr_handle_event_internal(SCR_ALERT_NOTIFICATION, SCR_EVENT_DESTROY_SCREEN, NULL);
						scr_alert_notification_state = SCR_ALERT_NOTIFICATION_STATE_NONE;
					  // draw sceen
						mlcd_fb_clear();
						scr_mngr_handle_event(SCR_EVENT_DRAW_SCREEN, (uint32_t)&draw_ctx);
				}
		} else if (scr_notifications_state != SCR_NOTIFICATIONS_STATE_NONE) {
				if (scr_notifications_state == SCR_NOTIFICATIONS_STATE_INIT) {
						scr_mngr_handle_event_internal(SCR_NOTIFICATIONS, SCR_EVENT_INIT_SCREEN, NULL);
					  // draw alert notification screen
						mlcd_fb_clear();
						scr_mngr_handle_event_internal(SCR_NOTIFICATIONS, SCR_EVENT_DRAW_SCREEN, (uint32_t)&draw_ctx);
						scr_notifications_state = SCR_NOTIFICATIONS_STATE_SHOW;
				} else if (scr_notifications_state == SCR_NOTIFICATIONS_STATE_SHOW) {
						scr_mngr_handle_event_internal(SCR_NOTIFICATIONS, SCR_EVENT_REFRESH_SCREEN, (uint32_t)&draw_ctx);
				} else if (scr_notifications_state == SCR_NOTIFICATIONS_STATE_REDRAW) {
						mlcd_fb_clear();
						scr_mngr_handle_event_internal(SCR_NOTIFICATIONS, SCR_EVENT_DRAW_SCREEN, (uint32_t)&draw_ctx);
						scr_notifications_state = SCR_NOTIFICATIONS_STATE_SHOW;
				} else if (scr_notifications_state == SCR_NOTIFICATIONS_STATE_CLOSE) {
						scr_mngr_handle_event_internal(SCR_NOTIFICATIONS, SCR_EVENT_DESTROY_SCREEN, NULL);
						scr_notifications_state = SCR_NOTIFICATIONS_STATE_NONE;
					  // draw sceen
						mlcd_fb_clear();
						scr_mngr_handle_event(SCR_EVENT_DRAW_SCREEN, (uint32_t)&draw_ctx);
				}
		} else {
				if (switch_to_screen != SCR_NOT_SET) {
						uint16_t old_screen = current_screen;
						uint16_t new_screen;
						// disable events
						current_screen = SCR_NOT_SET;
						do {
							// release memory used by old screen
							scr_mngr_handle_event_internal(old_screen, SCR_EVENT_DESTROY_SCREEN, NULL);
							new_screen = switch_to_screen;
							// initilize new screen
							scr_mngr_handle_event_internal(switch_to_screen, SCR_EVENT_INIT_SCREEN, switch_to_screen_param);
							old_screen = new_screen;
							// handle case when init redirects to other screen
						} while (new_screen != switch_to_screen);
						
						// draw screen
						mlcd_fb_clear();
					
						#ifdef OSSW_DEBUG
								uint32_t start_draw_ticks;
								app_timer_cnt_get(&start_draw_ticks);
						#endif
						scr_mngr_handle_event_internal(switch_to_screen, SCR_EVENT_DRAW_SCREEN, (uint32_t)&draw_ctx);
						#ifdef OSSW_DEBUG
								uint32_t end_draw_ticks;
								uint32_t total_diff;
								app_timer_cnt_get(&end_draw_ticks);
								app_timer_cnt_diff_compute(end_draw_ticks, start_draw_ticks, &total_diff);
								printf("DRAW: 0x%08x\r\n", total_diff);
						#endif
						// set new screen
						current_screen = switch_to_screen;
						switch_to_screen = SCR_NOT_SET;
				} else {
						if (redraw == true) {
							
								#ifdef OSSW_DEBUG
										uint32_t start_draw_ticks;
										app_timer_cnt_get(&start_draw_ticks);
								#endif
								scr_mngr_handle_event(SCR_EVENT_REFRESH_SCREEN, (uint32_t)&draw_ctx);
								#ifdef OSSW_DEBUG
										uint32_t end_draw_ticks;
										uint32_t total_diff;
										app_timer_cnt_get(&end_draw_ticks);
										app_timer_cnt_diff_compute(end_draw_ticks, start_draw_ticks, &total_diff);
										printf("REDRAW: 0x%08x\r\n", total_diff);
								#endif
								redraw = false;
						}
				}
		}
    mlcd_fb_flush_with_param(draw_ctx.force_colors);
}