PiScan/src/core/ScannerSM.cpp

379 lines
9.5 KiB
C++

/*
* ScannerStateMachine.cpp
*
* Created on: Feb 9, 2019
* Author: ezra
*/
#include <unistd.h>
#include <mutex>
#include "PiScan.h"
#include "ScannerSM.h"
#include "ListGenerator.h"
#include "loguru.hpp"
#define DELAY_TIMEOUT 2.0
using namespace piscan;
ScannerSM::ScannerSM(MessageReceiver& central, SystemList& dataSource) :
StateMachine(7), _centralQueue(central), _systems(dataSource), _externalHold(false), _manualMode(false) {
}
void ScannerSM::startScan(){
_externalHold = false;
LOG_F(1, "ExtEvent: startScan");
BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY(ST_SCAN)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(ST_SCAN)
TRANSITION_MAP_ENTRY(ST_SCAN)
TRANSITION_MAP_ENTRY(ST_SCAN)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
END_TRANSITION_MAP(NULL)
}
void ScannerSM::holdScan(std::vector<int> index){
_externalHold = true;
{
std::lock_guard<std::mutex> lock(_holdMutex);
_holdIndex = index;
}
LOG_F(1, "ExtEvent: holdScan");
BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(ST_HOLD)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
END_TRANSITION_MAP(NULL)
}
void ScannerSM::stopScanner(){
LOG_F(1, "ExtEvent: stopScanner");
BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY(ST_SAVEALL)
TRANSITION_MAP_ENTRY(ST_SAVEALL)
TRANSITION_MAP_ENTRY(ST_SAVEALL)
TRANSITION_MAP_ENTRY(ST_SAVEALL)
TRANSITION_MAP_ENTRY(ST_SAVEALL)
TRANSITION_MAP_ENTRY(ST_SAVEALL)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
END_TRANSITION_MAP(NULL)
}
void ScannerSM::manualEntry(uint32_t* freq){
LOG_F(1, "ExtEvent: manualEntry");
BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(ST_MANUAL)
TRANSITION_MAP_ENTRY(ST_MANUAL)
TRANSITION_MAP_ENTRY(ST_MANUAL)
TRANSITION_MAP_ENTRY(ST_MANUAL)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
TRANSITION_MAP_ENTRY(EVENT_IGNORED)
END_TRANSITION_MAP(new EventData(freq))
}
void ScannerSM::ST_Load(EventData* data){
loguru::set_thread_name("Scanner");
DLOG_F(9, "ST_Load");
_systems.populateFromFile();
LOG_F(INFO, "Loaded %u systems", _systems.size());
//_currentSystem = _systems[0];
_systems.sortBins(getTunerSampleRate());
// do not issue event - SM will wait until an event is generated before proceeding
//InternalEvent(ST_SCAN);
//auto message = std::make_shared<ControllerMessage>(SCANNER_SM, ControllerMessage::NOTIFY_READY);
//_centralQueue.giveMessage(message);
LOG_F(1, "ScannerSM ready");
notifyReady();
}
void ScannerSM::ST_Scan(EventData* data){
DLOG_F(9, "ST_Scan");
if(currentState != lastState){
_squelchHits = 0;
DLOG_F(6, "State change: %i -> %i", lastState, currentState);
}
_enableAudioOut(false);
_currentContext.state = ScannerContext::SCAN;
_manualMode = false;
if(currentState != lastState)
_broadcastContextUpdate();
if (!_squelchHits || (currentState != lastState)) {
_currentEntry = _systems.getNextEntry();
}
if (_currentEntry->hasSignal()) {
_squelchHits++;
if (_squelchHits >= SQUELCH_TRIGGER_HITS) {
LOG_F(2, "Signal found: %s", _currentEntry->tag().c_str());
InternalEvent(ST_RECEIVE);
} else {
InternalEvent(ST_SCAN);
}
} else {
_squelchHits = 0;
InternalEvent(ST_SCAN);
}
}
void ScannerSM::ST_Hold(EventData* data){
DLOG_F(9, "ST_Hold");
if(currentState != lastState)
DLOG_F(6, "State change: %i -> %i", lastState, currentState);
bool indexHold = false;
{
std::lock_guard < std::mutex > lock(_holdMutex);
if (_externalHold && _holdIndex.size() > 0) {
_currentEntry = _systems.getEntryByIndex(_holdIndex);
LOG_F(1, "Index hold");
_holdIndex.clear();
indexHold = true;
}
}
/* don't hold on dummy channels */
while(_currentEntry->isDummy()){
_currentEntry->hasSignal();
_currentEntry = _systems.getNextEntry();
}
_enableAudioOut(false);
_currentContext.state = ScannerContext::HOLD;
if(currentState != lastState || indexHold)
_broadcastContextUpdate();
/* start receive if signal active */
if (_currentEntry->hasSignal()) {
LOG_F(5, "Receiving signal");
InternalEvent(ST_RECEIVE);
}
/* stay in hold if state was triggered externally */
else if(_externalHold){
InternalEvent(ST_HOLD);
}
/* check timeout counter if entry has resume delay enabled */
else if(_currentEntry->delay() && timeoutStart != 0){
if(std::difftime(std::time(nullptr), timeoutStart) < _currentEntry->delay()){
InternalEvent(ST_HOLD);
}
else {
LOG_F(3, "Delay timed out, resuming scan");
InternalEvent(ST_SCAN);
}
}
/* entry does not have delay and hold was internal */
else{
LOG_F(3, "Resuming scan");
InternalEvent(ST_SCAN);
}
/* wait for 1ms */
usleep(1000);
}
void ScannerSM::ST_Receive(EventData* data){
DLOG_F(9, "ST_Receive");
if(currentState != lastState)
DLOG_F(6, "State change: %i -> %i", lastState, currentState);
_enableAudioOut(true);
_currentContext.state = ScannerContext::RECEIVE;
if(currentState != lastState)
_broadcastContextUpdate();
if (_currentEntry->hasSignal()) {
InternalEvent(ST_RECEIVE);
}
else{
LOG_F(5, "Signal lost");
InternalEvent(ST_HOLD);
timeoutStart = std::time(nullptr);
return;
}
/* wait for 1ms */
usleep(1000);
}
void ScannerSM::ST_Manual(EventData* data){
DLOG_F(9, "ST_Manual");
if(currentState != lastState)
DLOG_F(6, "State change: %i -> %i", lastState, currentState);
uint32_t* freq = reinterpret_cast<uint32_t*>(data->data);
LOG_F(1, "Setting manual frequency to %.4lfMHz", (*freq / 1E6));
/* delete old manual entry */
//if(_manualEntry != nullptr)
//delete _manualEntry;
_manualEntry = std::make_shared<FMChannel>(*freq, "", false, false);
delete freq;
_currentEntry = _manualEntry;
_externalHold = true;
_manualMode = true;
InternalEvent(ST_HOLD);
}
void ScannerSM::ST_SaveAll(EventData* data){
DLOG_F(9, "ST_SaveAll");
LOG_F(1, "Saving database");
_systems.writeToFile();
InternalEvent(ST_STOPPED);
}
void ScannerSM::ST_Stopped(EventData* data){
DLOG_F(9, "ST_Stopped");
stop(false);
//auto message = std::make_shared<ControllerMessage>(SCANNER_SM, ControllerMessage::NOTIFY_STOPPED);
//_centralQueue.giveMessage(message);
LOG_F(1, "ScannerSM stopped");
notifyDeinit();
}
void ScannerSM::_broadcastContextUpdate() {
DLOG_F(6, "Broadcasting context");
std::lock_guard<std::mutex> lock(_contextMutex);
if (_currentContext.state != ScannerContext::SCAN)
{
if (_manualMode)
{
_currentContext.systemTag = "Manual";
_currentContext.entryTag = "Manual entry";
_currentContext.entryIndex = "MAN";
}
else
{
//_currentContext.systemTag = _currentSystem->tag();
_currentContext.systemTag = _systems[_currentEntry->getSysIndex()]->tag();
_currentContext.entryTag = _currentEntry->tag();
_currentContext.entryIndex = std::to_string(_currentEntry->getSysIndex()) + "-" + std::to_string(_currentEntry->getEntryIndex());
}
_currentContext.frequency = _currentEntry->freq();
_currentContext.modulation = _currentEntry->modulation();
}
else {
_currentContext.clearFields();
}
auto message = std::make_shared<ServerMessage>(SCANNER_SM, ServerMessage::CONTEXT_UPDATE, new ScannerContext(_currentContext));
_centralQueue.giveMessage(message);
}
void ScannerSM::_enableAudioOut(bool en){
/*Message* message;
if(en){
message = new AudioMessage(SCANNER_SM, AudioMessage::ENABLE_OUTPUT);
}
else{
message = new AudioMessage(SCANNER_SM, AudioMessage::DISABLE_OUTPUT);
}
_centralQueue.giveMessage(*message);*/
//TODO temporary
//rtl_fm_mute((int)(!en));
//auto message = std::make_shared<DemodMessage>(SCANNER_SM, DemodMessage::OPEN_AUDIO, (void*) !en);
//_centralQueue.giveMessage(message);
piscan::audioMute(en);
}
void ScannerSM::giveMessage(std::shared_ptr<Message> message) {
auto msg = std::dynamic_pointer_cast<ScannerMessage>(message);
DLOG_F(7, "Message rcv - src:%i | type:%i", msg->source, msg->type);
switch (msg->type) {
/* stop call */
case ScannerMessage::STOP:
stopScanner();
break;
/* handle client request */
case ScannerMessage::CLIENT_REQUEST:
_handleRequest(*(static_cast<ClientRequest*>(msg->pData)));
break;
/* handle external state trigger */
case ScannerMessage::STATE_CHANGE:
auto newState = static_cast<States>(reinterpret_cast<size_t>(msg->pData) & 0xFF);
switch (newState) {
case ScannerMessage::STATE_SCAN:
startScan();
break;
case ScannerMessage::STATE_HOLD:
holdScan();
break;
default:
DLOG_F(WARNING, "Invalid state request");
break;
}
break;
}
}
void ScannerSM::_handleRequest(ClientRequest& request) {
ClientRequest* rq = &request;
if (rq->rqInfo.type == SCANNER_FUNCTION) {
switch (rq->rqInfo.subType) {
case SCANNER_STATE_SCAN:
startScan();
break;
case SCANNER_STATE_HOLD:
if(request.pData != nullptr){
std::vector<int>* indexData = reinterpret_cast<std::vector<int>*>(request.pData);
holdScan(*indexData);
delete indexData;
}
else
holdScan();
break;
case SCANNER_STATE_MANUAL:
manualEntry(reinterpret_cast<uint32_t*>(rq->pData));
break;
default:
break;
}
}
else if (rq->rqInfo.type == GET_CONTEXT){
std::unique_lock<std::mutex> lock(_contextMutex);
ScannerContext* context = new ScannerContext(_currentContext);
lock.unlock();
rq->connection->scannerContextRequestCallback(rq->rqHandle, context);
}
else{
DLOG_F(WARNING, "Invalid scanner request type: %i", rq->rqInfo.type);
}
delete rq;
}
ScannerContext ScannerSM::getCurrentContext(){
std::unique_lock<std::mutex> lock(_contextMutex);
return ScannerContext(_currentContext);
}