399 Zeilen
12 KiB
C++
399 Zeilen
12 KiB
C++
/**
|
|
* server/src/hardware/protocol/loconet.cpp
|
|
*
|
|
* This file is part of the traintastic source code.
|
|
*
|
|
* Copyright (C) 2019-2020 Reinder Feenstra
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "loconet.hpp"
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include "../../../core/eventloop.hpp"
|
|
#include "../../../core/traintastic.hpp"
|
|
#include "../../commandstation/commandstation.hpp"
|
|
#include "../../input/loconetinput.hpp"
|
|
#include "../../../core/attributes.hpp"
|
|
|
|
namespace LocoNet {
|
|
|
|
void updateDecoderSpeed(const std::shared_ptr<Decoder>& decoder, uint8_t speed)
|
|
{
|
|
decoder->emergencyStop.setValueInternal(speed == SPEED_ESTOP);
|
|
|
|
if(speed == SPEED_STOP || speed == SPEED_ESTOP)
|
|
decoder->speedStep.setValueInternal(0);
|
|
else
|
|
decoder->speedStep.setValueInternal(((speed - 1) * decoder->speedSteps) / (SPEED_MAX - 1));
|
|
}
|
|
|
|
LocoNet::LocoNet(Object& _parent, const std::string& parentPropertyName, std::function<bool(const Message&)> send) :
|
|
SubObject(_parent, parentPropertyName),
|
|
m_commandStation{dynamic_cast<CommandStation*>(&_parent)},
|
|
m_send{std::move(send)},
|
|
m_debugLog{false},
|
|
m_queryLocoSlots{SLOT_UNKNOWN},
|
|
commandStation{this, "command_station", LocoNetCommandStation::Custom, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
|
[this](LocoNetCommandStation value)
|
|
{
|
|
switch(value)
|
|
{
|
|
case LocoNetCommandStation::Custom:
|
|
break;
|
|
|
|
case LocoNetCommandStation::DigikeijsDR5000:
|
|
break;
|
|
|
|
case LocoNetCommandStation::UhlenbrockIntellibox:
|
|
break;
|
|
}
|
|
}},
|
|
debugLog{this, "debug_log", m_debugLog, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
|
[this](bool value)
|
|
{
|
|
m_debugLog = value;
|
|
}}
|
|
{
|
|
assert(m_send);
|
|
|
|
Attributes::addEnabled(commandStation, m_commandStation && !m_commandStation->online);
|
|
Attributes::addValues(commandStation, LocoNetCommandStationValues);
|
|
m_interfaceItems.add(commandStation);
|
|
m_interfaceItems.add(debugLog);
|
|
}
|
|
|
|
bool LocoNet::send(const Message& message)
|
|
{
|
|
if(m_debugLog)
|
|
logDebug("tx: " + to_string(message));
|
|
assert(isValid(message));
|
|
return m_send(message);
|
|
}
|
|
|
|
void LocoNet::send(uint16_t address, Message& message, uint8_t& slot)
|
|
{
|
|
if((slot = m_slots.getSlot(address)) != SLOT_UNKNOWN)
|
|
{
|
|
updateChecksum(message);
|
|
send(message);
|
|
}
|
|
else // try get a slot
|
|
{
|
|
std::byte* ptr = reinterpret_cast<std::byte*>(&message);
|
|
|
|
auto it = m_slotRequests.find(address);
|
|
if(it == m_slotRequests.end())
|
|
{
|
|
m_slotRequests[address].assign(ptr, ptr + message.size());
|
|
send(LocoAdr{address});
|
|
}
|
|
else
|
|
it->second.insert(it->second.end(), ptr, ptr + message.size());
|
|
}
|
|
}
|
|
|
|
void LocoNet::receive(const Message& message)
|
|
{
|
|
// NOTE: this function is called async!
|
|
|
|
assert(isValid(message));
|
|
|
|
if(m_debugLog)
|
|
EventLoop::call([this, log="rx: " + to_string(message)](){ logDebug(log); });
|
|
|
|
switch(message.opCode)
|
|
{
|
|
case OPC_GPON:
|
|
EventLoop::call(
|
|
[this]()
|
|
{
|
|
if(m_commandStation)
|
|
{
|
|
m_commandStation->emergencyStop.setValueInternal(false);
|
|
m_commandStation->trackVoltageOff.setValueInternal(false);
|
|
}
|
|
});
|
|
break;
|
|
|
|
case OPC_GPOFF:
|
|
EventLoop::call(
|
|
[this]()
|
|
{
|
|
if(m_commandStation)
|
|
m_commandStation->trackVoltageOff.setValueInternal(true);
|
|
});
|
|
break;
|
|
|
|
case OPC_IDLE:
|
|
EventLoop::call(
|
|
[this]()
|
|
{
|
|
if(m_commandStation)
|
|
m_commandStation->emergencyStop.setValueInternal(true);
|
|
});
|
|
break;
|
|
|
|
case OPC_LOCO_SPD:
|
|
EventLoop::call(
|
|
[this, locoSpd=*static_cast<const LocoSpd*>(&message)]()
|
|
{
|
|
if(auto decoder = getDecoder(locoSpd.slot))
|
|
updateDecoderSpeed(decoder, locoSpd.speed);
|
|
});
|
|
break;
|
|
|
|
case OPC_LOCO_DIRF:
|
|
EventLoop::call(
|
|
[this, locoDirF=*static_cast<const LocoDirF*>(&message)]()
|
|
{
|
|
if(auto decoder = getDecoder(locoDirF.slot))
|
|
{
|
|
decoder->direction.setValueInternal(locoDirF.direction());
|
|
decoder->setFunctionValue(0, locoDirF.f0());
|
|
decoder->setFunctionValue(1, locoDirF.f1());
|
|
decoder->setFunctionValue(2, locoDirF.f2());
|
|
decoder->setFunctionValue(3, locoDirF.f3());
|
|
decoder->setFunctionValue(4, locoDirF.f4());
|
|
}
|
|
});
|
|
break;
|
|
|
|
case OPC_LOCO_SND:
|
|
EventLoop::call(
|
|
[this, locoSnd=*static_cast<const LocoSnd*>(&message)]()
|
|
{
|
|
if(auto decoder = getDecoder(locoSnd.slot))
|
|
{
|
|
decoder->setFunctionValue(5, locoSnd.f5());
|
|
decoder->setFunctionValue(6, locoSnd.f6());
|
|
decoder->setFunctionValue(7, locoSnd.f7());
|
|
decoder->setFunctionValue(8, locoSnd.f8());
|
|
}
|
|
});
|
|
break;
|
|
|
|
case OPC_LOCO_F9F12:
|
|
EventLoop::call(
|
|
[this, locoF9F12=*static_cast<const LocoF9F12*>(&message)]()
|
|
{
|
|
if(auto decoder = getDecoder(locoF9F12.slot))
|
|
{
|
|
decoder->setFunctionValue(9, locoF9F12.f9());
|
|
decoder->setFunctionValue(10, locoF9F12.f10());
|
|
decoder->setFunctionValue(11, locoF9F12.f11());
|
|
decoder->setFunctionValue(12, locoF9F12.f12());
|
|
}
|
|
});
|
|
break;
|
|
|
|
case OPC_INPUT_REP:
|
|
EventLoop::call(
|
|
[this, inputRep=*static_cast<const InputRep*>(&message)]()
|
|
{
|
|
auto it = m_inputs.find(1 + inputRep.address());
|
|
if(it != m_inputs.end())
|
|
it->second->valueChanged(inputRep.value());
|
|
});
|
|
break;
|
|
|
|
case OPC_SL_RD_DATA:
|
|
EventLoop::call(
|
|
[this, slotReadData=*static_cast<const SlotReadData*>(&message)]()
|
|
{
|
|
if(m_queryLocoSlots == slotReadData.slot)
|
|
{
|
|
m_queryLocoSlots++;
|
|
if(m_queryLocoSlots <= SLOT_LOCO_MAX)
|
|
send(RequestSlotData(m_queryLocoSlots));
|
|
else
|
|
m_queryLocoSlots = SLOT_UNKNOWN; // done
|
|
}
|
|
|
|
if(slotReadData.isBusy() || slotReadData.isActive())
|
|
{
|
|
m_slots.set(slotReadData.address(), slotReadData.slot);
|
|
|
|
logDebug("slot " + std::to_string(slotReadData.slot) + " = " + std::to_string(slotReadData.address()));
|
|
|
|
if(auto decoder = getDecoder(slotReadData.slot, false))
|
|
{
|
|
updateDecoderSpeed(decoder, slotReadData.spd);
|
|
decoder->direction.setValueInternal(slotReadData.direction());
|
|
decoder->setFunctionValue(0, slotReadData.f0());
|
|
decoder->setFunctionValue(1, slotReadData.f1());
|
|
decoder->setFunctionValue(2, slotReadData.f2());
|
|
decoder->setFunctionValue(3, slotReadData.f3());
|
|
decoder->setFunctionValue(4, slotReadData.f4());
|
|
decoder->setFunctionValue(5, slotReadData.f5());
|
|
decoder->setFunctionValue(6, slotReadData.f6());
|
|
decoder->setFunctionValue(7, slotReadData.f7());
|
|
decoder->setFunctionValue(8, slotReadData.f8());
|
|
}
|
|
}
|
|
else
|
|
logDebug("slot " + std::to_string(slotReadData.slot) + " = FREE");
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LocoNet::emergencyStopChanged(bool value)
|
|
{
|
|
if(value)
|
|
send(Idle());
|
|
else if(m_commandStation && !m_commandStation->trackVoltageOff)
|
|
send(GlobalPowerOn());
|
|
}
|
|
|
|
void LocoNet::trackVoltageOffChanged(bool value)
|
|
{
|
|
if(!value)
|
|
send(GlobalPowerOn());
|
|
else
|
|
send(GlobalPowerOff());
|
|
}
|
|
|
|
void LocoNet::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
|
{
|
|
logDebug("LocoNet::decoderChanged");
|
|
|
|
if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::SpeedStep))
|
|
{
|
|
LocoSpd message{static_cast<uint8_t>(decoder.emergencyStop ? 1 : (decoder.speedStep > 0 ? 1 + decoder.speedStep : 0))};
|
|
send(decoder.address, message);
|
|
}
|
|
|
|
if(has(changes, DecoderChangeFlags::FunctionValue | DecoderChangeFlags::Direction))
|
|
{
|
|
if(functionNumber <= 4 || has(changes, DecoderChangeFlags::Direction))
|
|
{
|
|
LocoDirF message{
|
|
decoder.direction,
|
|
decoder.getFunctionValue(0),
|
|
decoder.getFunctionValue(1),
|
|
decoder.getFunctionValue(2),
|
|
decoder.getFunctionValue(3),
|
|
decoder.getFunctionValue(4)};
|
|
send(decoder.address, message);
|
|
}
|
|
else if(functionNumber <= 8)
|
|
{
|
|
LocoSnd message{
|
|
decoder.getFunctionValue(5),
|
|
decoder.getFunctionValue(6),
|
|
decoder.getFunctionValue(7),
|
|
decoder.getFunctionValue(8)};
|
|
send(decoder.address, message);
|
|
}
|
|
else if(functionNumber <= 12)
|
|
{
|
|
LocoF9F12 message{
|
|
decoder.getFunctionValue(9),
|
|
decoder.getFunctionValue(10),
|
|
decoder.getFunctionValue(11),
|
|
decoder.getFunctionValue(12)};
|
|
send(decoder.address, message);
|
|
}
|
|
else if(functionNumber <= 19)
|
|
{
|
|
LocoF13F19 message{
|
|
decoder.getFunctionValue(13),
|
|
decoder.getFunctionValue(14),
|
|
decoder.getFunctionValue(15),
|
|
decoder.getFunctionValue(16),
|
|
decoder.getFunctionValue(17),
|
|
decoder.getFunctionValue(18),
|
|
decoder.getFunctionValue(19)};
|
|
send(decoder.address, message);
|
|
}
|
|
else if(functionNumber == 20 || functionNumber == 28)
|
|
{
|
|
LocoF20F28 message{
|
|
decoder.getFunctionValue(20),
|
|
decoder.getFunctionValue(28)};
|
|
send(decoder.address, message);
|
|
}
|
|
else if(functionNumber <= 27)
|
|
{
|
|
LocoF21F27 message{
|
|
decoder.getFunctionValue(21),
|
|
decoder.getFunctionValue(22),
|
|
decoder.getFunctionValue(23),
|
|
decoder.getFunctionValue(24),
|
|
decoder.getFunctionValue(25),
|
|
decoder.getFunctionValue(26),
|
|
decoder.getFunctionValue(27)};
|
|
send(decoder.address, message);
|
|
}
|
|
else
|
|
logWarning("Function F" + std::to_string(functionNumber) + " not supported");
|
|
}
|
|
}
|
|
|
|
void LocoNet::queryLocoSlots()
|
|
{
|
|
m_queryLocoSlots = SLOT_LOCO_MIN;
|
|
send(RequestSlotData(m_queryLocoSlots));
|
|
}
|
|
|
|
std::shared_ptr<Decoder> LocoNet::getDecoder(uint8_t slot, bool request)
|
|
{
|
|
if(slot < SLOT_LOCO_MIN || slot > SLOT_LOCO_MAX)
|
|
return nullptr;
|
|
|
|
if(m_commandStation)
|
|
{
|
|
const uint16_t address = m_slots.getAddress(slot);
|
|
if(address != 0)
|
|
{
|
|
auto decoder = m_commandStation->getDecoder(DecoderProtocol::DCC, address, isLongAddress(address));
|
|
if(!decoder)
|
|
decoder = m_commandStation->getDecoder(DecoderProtocol::Auto, address);
|
|
return decoder;
|
|
}
|
|
else if(request)
|
|
send(RequestSlotData(slot));
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool LocoNet::isInputAddressAvailable(uint16_t address)
|
|
{
|
|
return m_inputs.find(address) == m_inputs.end();
|
|
}
|
|
|
|
bool LocoNet::addInput(const std::shared_ptr<LocoNetInput>& input)
|
|
{
|
|
assert(input->loconet.value().get() == this);
|
|
if(isInputAddressAvailable(input->address))
|
|
{
|
|
m_inputs.insert({input->address, input});
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void LocoNet::removeInput(const std::shared_ptr<LocoNetInput>& input)
|
|
{
|
|
assert(input->loconet.value().get() == this);
|
|
m_inputs.erase(m_inputs.find(input->address));
|
|
}
|
|
|
|
}
|