@@ -27,7 +27,7 @@ static Block::UID getBlockUIDFromSerialNumber (const uint8* serial) noexcept | |||
{ | |||
Block::UID n = {}; | |||
for (int i = 0; i < (int) sizeof (BlocksProtocol::BlockSerialNumber); ++i) | |||
for (int i = 0; i < int (BlocksProtocol::BlockSerialNumber::maxLength); ++i) | |||
n += n * 127 + serial[i]; | |||
return n; | |||
@@ -35,15 +35,15 @@ static Block::UID getBlockUIDFromSerialNumber (const uint8* serial) noexcept | |||
static Block::UID getBlockUIDFromSerialNumber (const BlocksProtocol::BlockSerialNumber& serial) noexcept | |||
{ | |||
return getBlockUIDFromSerialNumber (serial.serial); | |||
return getBlockUIDFromSerialNumber (serial.data); | |||
} | |||
static Block::UID getBlockUIDFromSerialNumber (const juce::String& serial) noexcept | |||
{ | |||
if (serial.length() < (int) sizeof (BlocksProtocol::BlockSerialNumber)) | |||
if (serial.length() < int (BlocksProtocol::BlockSerialNumber::maxLength)) | |||
{ | |||
jassertfalse; | |||
return getBlockUIDFromSerialNumber (serial.paddedRight ('0', sizeof (BlocksProtocol::BlockSerialNumber))); | |||
return getBlockUIDFromSerialNumber (serial.paddedRight ('0', BlocksProtocol::BlockSerialNumber::maxLength)); | |||
} | |||
return getBlockUIDFromSerialNumber ((const uint8*) serial.toRawUTF8()); | |||
@@ -107,6 +107,13 @@ public: | |||
/** Returns true if this block is connected and active. */ | |||
virtual bool isConnected() const = 0; | |||
/** Returns the time this block object was connected to the topology. | |||
Only valid when isConnected == true. | |||
@see isConnected | |||
*/ | |||
virtual Time getConnectionTime() const = 0; | |||
/** Returns true if this block is directly connected to the application, | |||
as opposed to only being connected to a different block via a connection port. | |||
@@ -123,36 +123,6 @@ using BatteryCharging = IntegerWithBitSize<1>; | |||
*/ | |||
using ConnectorPort = IntegerWithBitSize<5>; | |||
//============================================================================== | |||
/** Structure describing a block's serial number | |||
@tags{Blocks} | |||
*/ | |||
struct BlockSerialNumber | |||
{ | |||
uint8 serial[16]; | |||
bool isValid() const noexcept | |||
{ | |||
for (auto c : serial) | |||
if (c == 0) | |||
return false; | |||
return isAnyControlBlock() || isPadBlock() || isSeaboardBlock(); | |||
} | |||
bool isPadBlock() const noexcept { return hasPrefix ("LPB") || hasPrefix ("LPM"); } | |||
bool isLiveBlock() const noexcept { return hasPrefix ("LIC"); } | |||
bool isLoopBlock() const noexcept { return hasPrefix ("LOC"); } | |||
bool isDevCtrlBlock() const noexcept { return hasPrefix ("DCB"); } | |||
bool isTouchBlock() const noexcept { return hasPrefix ("TCB"); } | |||
bool isSeaboardBlock() const noexcept { return hasPrefix ("SBB"); } | |||
bool isAnyControlBlock() const noexcept { return isLiveBlock() || isLoopBlock() || isDevCtrlBlock() || isTouchBlock(); } | |||
bool hasPrefix (const char* prefix) const noexcept { return memcmp (serial, prefix, 3) == 0; } | |||
}; | |||
//============================================================================== | |||
/** Structure for generic block data | |||
@@ -171,6 +141,11 @@ struct BlockStringData | |||
return length > 0; | |||
} | |||
juce::String asString() const | |||
{ | |||
return juce::String ((const char*) data, length); | |||
} | |||
bool operator== (const BlockStringData& other) const | |||
{ | |||
if (length != other.length) | |||
@@ -192,6 +167,34 @@ struct BlockStringData | |||
using VersionNumber = BlockStringData<21>; | |||
using BlockName = BlockStringData<33>; | |||
//============================================================================== | |||
/** Structure describing a block's serial number | |||
@tags{Blocks} | |||
*/ | |||
struct BlockSerialNumber : public BlockStringData<16> | |||
{ | |||
bool isValid() const noexcept | |||
{ | |||
for (auto c : data) | |||
if (c == 0) | |||
return false; | |||
return isAnyControlBlock() || isPadBlock() || isSeaboardBlock(); | |||
} | |||
bool isPadBlock() const noexcept { return hasPrefix ("LPB") || hasPrefix ("LPM"); } | |||
bool isLiveBlock() const noexcept { return hasPrefix ("LIC"); } | |||
bool isLoopBlock() const noexcept { return hasPrefix ("LOC"); } | |||
bool isDevCtrlBlock() const noexcept { return hasPrefix ("DCB"); } | |||
bool isTouchBlock() const noexcept { return hasPrefix ("TCB"); } | |||
bool isSeaboardBlock() const noexcept { return hasPrefix ("SBB"); } | |||
bool isAnyControlBlock() const noexcept { return isLiveBlock() || isLoopBlock() || isDevCtrlBlock() || isTouchBlock(); } | |||
bool hasPrefix (const char* prefix) const noexcept { return memcmp (data, prefix, 3) == 0; } | |||
}; | |||
//============================================================================== | |||
/** Structure for the device status | |||
@@ -214,6 +217,25 @@ struct DeviceConnection | |||
{ | |||
TopologyIndex device1, device2; | |||
ConnectorPort port1, port2; | |||
bool operator== (const DeviceConnection& other) const | |||
{ | |||
return isEqual (other); | |||
} | |||
bool operator!= (const DeviceConnection& other) const | |||
{ | |||
return ! isEqual (other); | |||
} | |||
private: | |||
bool isEqual (const DeviceConnection& other) const | |||
{ | |||
return device1 == other.device1 | |||
&& device2 == other.device2 | |||
&& port1 == other.port1 | |||
&& port2 == other.port2; | |||
} | |||
}; | |||
//============================================================================== | |||
@@ -445,7 +467,7 @@ static constexpr uint32 controlBlockStackSize = 800; | |||
enum BitSizes | |||
{ | |||
topologyMessageHeader = MessageType::bits + ProtocolVersion::bits + DeviceCount::bits + ConnectionCount::bits, | |||
topologyDeviceInfo = sizeof (BlockSerialNumber) * 7 + BatteryLevel::bits + BatteryCharging::bits, | |||
topologyDeviceInfo = BlockSerialNumber::maxLength * 7 + BatteryLevel::bits + BatteryCharging::bits, | |||
topologyConnectionInfo = topologyIndexBits + ConnectorPort::bits + topologyIndexBits + ConnectorPort::bits, | |||
typeDeviceAndTime = MessageType::bits + PacketTimestampOffset::bits, | |||
@@ -164,8 +164,11 @@ struct HostPacketDecoder | |||
{ | |||
DeviceStatus status; | |||
for (uint32 i = 0; i < sizeof (BlockSerialNumber); ++i) | |||
status.serialNumber.serial[i] = (uint8) reader.readBits (7); | |||
for (uint32 i = 0; i < BlockSerialNumber::maxLength; ++i) | |||
{ | |||
status.serialNumber.data[i] = (uint8) reader.readBits (7); | |||
++status.serialNumber.length; | |||
} | |||
status.index = (TopologyIndex) reader.readBits (topologyIndexBits); | |||
status.batteryLevel = reader.read<BatteryLevel>(); | |||
@@ -34,19 +34,16 @@ public: | |||
struct LEDGridImplementation; | |||
struct LEDRowImplementation; | |||
BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, | |||
Detector& detectorToUse, | |||
BlocksProtocol::VersionNumber version, | |||
BlocksProtocol::BlockName blockName, | |||
bool isMasterBlock) | |||
: Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), | |||
juce::String ((const char*) version.data, version.length), | |||
juce::String ((const char*) blockName.data, blockName.length)), | |||
modelData (serial), | |||
BlockImplementation (Detector& detectorToUse, const DeviceInfo& deviceInfo) | |||
: Block (deviceInfo.serial.asString(), | |||
deviceInfo.version.asString(), | |||
deviceInfo.name.asString()), | |||
modelData (deviceInfo.serial), | |||
remoteHeap (modelData.programAndHeapSize), | |||
detector (&detectorToUse), | |||
isMaster (isMasterBlock) | |||
detector (&detectorToUse) | |||
{ | |||
markReconnected (deviceInfo); | |||
if (modelData.hasTouchSurface) | |||
touchSurface.reset (new TouchSurfaceImplementation (*this)); | |||
@@ -77,13 +74,25 @@ public: | |||
{ | |||
if (auto surface = dynamic_cast<TouchSurfaceImplementation*> (touchSurface.get())) | |||
surface->disableTouchSurface(); | |||
connectionTime = Time(); | |||
} | |||
void markReconnected (const DeviceInfo& deviceInfo) | |||
{ | |||
versionNumber = asString (deviceInfo.version); | |||
name = asString (deviceInfo.name); | |||
if (wasPowerCycled()) | |||
resetPowerCycleFlag(); | |||
if (connectionTime == Time()) | |||
connectionTime = Time::getCurrentTime(); | |||
versionNumber = deviceInfo.version.asString(); | |||
name = deviceInfo.name.asString(); | |||
isMaster = deviceInfo.isMaster; | |||
masterUID = deviceInfo.masterUid; | |||
batteryCharging = deviceInfo.batteryCharging; | |||
batteryLevel = deviceInfo.batteryLevel; | |||
topologyIndex = deviceInfo.index; | |||
setProgram (nullptr); | |||
remoteHeap.resetDeviceStateToUnknown(); | |||
@@ -120,6 +129,7 @@ public: | |||
bool isHardwareBlock() const override { return true; } | |||
juce::Array<Block::ConnectionPort> getPorts() const override { return modelData.ports; } | |||
bool isConnected() const override { return detector && detector->isConnected (uid); } | |||
juce::Time getConnectionTime() const override { return connectionTime; } | |||
bool isMasterBlock() const override { return isMaster; } | |||
Block::UID getConnectedMasterUID() const override { return masterUID; } | |||
int getRotation() const override { return rotation; } | |||
@@ -159,24 +169,12 @@ public: | |||
float getBatteryLevel() const override | |||
{ | |||
if (detector == nullptr) | |||
return 0.0f; | |||
if (auto status = detector->getLastStatus (uid)) | |||
return status->batteryLevel.toUnipolarFloat(); | |||
return 0.0f; | |||
return batteryLevel.toUnipolarFloat(); | |||
} | |||
bool isBatteryCharging() const override | |||
{ | |||
if (detector == nullptr) | |||
return false; | |||
if (auto status = detector->getLastStatus (uid)) | |||
return status->batteryCharging.get() != 0; | |||
return false; | |||
return batteryCharging.get() > 0; | |||
} | |||
bool supportsGraphics() const override | |||
@@ -186,10 +184,7 @@ public: | |||
int getDeviceIndex() const noexcept | |||
{ | |||
if (detector == nullptr) | |||
return -1; | |||
return isConnected() ? detector->getIndexFromDeviceID (uid) : -1; | |||
return isConnected() ? topologyIndex : -1; | |||
} | |||
template <typename PacketBuilder> | |||
@@ -220,10 +215,15 @@ public: | |||
programEventListeners.call ([&] (ProgramEventListener& l) { l.handleProgramEvent (*this, m); }); | |||
} | |||
static BlockImplementation* getFrom (Block* b) noexcept | |||
{ | |||
jassert (dynamic_cast<BlockImplementation*> (b) != nullptr); | |||
return dynamic_cast<BlockImplementation*> (b); | |||
} | |||
static BlockImplementation* getFrom (Block& b) noexcept | |||
{ | |||
jassert (dynamic_cast<BlockImplementation*> (&b) != nullptr); | |||
return dynamic_cast<BlockImplementation*> (&b); | |||
return getFrom (&b); | |||
} | |||
//============================================================================== | |||
@@ -584,6 +584,13 @@ private: | |||
bool isMaster = false; | |||
Block::UID masterUID = {}; | |||
BlocksProtocol::BatteryLevel batteryLevel {}; | |||
BlocksProtocol::BatteryCharging batteryCharging {}; | |||
BlocksProtocol::TopologyIndex topologyIndex {}; | |||
Time connectionTime {}; | |||
std::pair<int, int> position; | |||
int rotation = 0; | |||
friend Detector; | |||
@@ -29,55 +29,6 @@ namespace | |||
{ | |||
return static_cast<Block::Timestamp> (timestamp); | |||
} | |||
template <typename V> | |||
static int removeUnusedBlocksFromMap (std::map<Block::UID, V>& mapToClean, const juce::Array<DeviceInfo>& currentDevices) | |||
{ | |||
int numRemoved = 0; | |||
for (auto iter = mapToClean.begin(); iter != mapToClean.end();) | |||
{ | |||
bool found = false; | |||
for (auto& info : currentDevices) | |||
{ | |||
if (info.uid == iter->first) | |||
{ | |||
found = true; | |||
break; | |||
} | |||
} | |||
if (found) | |||
{ | |||
++iter; | |||
} | |||
else | |||
{ | |||
mapToClean.erase (iter++); | |||
++numRemoved; | |||
} | |||
} | |||
return numRemoved; | |||
} | |||
/* Returns true new element added to map or value of existing key changed */ | |||
template <typename V> | |||
static bool insertOrAssign (std::map<Block::UID, V>& map, const Block::UID& key, const V& value) | |||
{ | |||
const auto result = map.emplace (std::make_pair (key, value)); | |||
if (! result.second) | |||
{ | |||
if (result.first->second == value) | |||
return false; | |||
result.first->second = value; | |||
} | |||
return true; | |||
} | |||
} | |||
template <typename Detector> | |||
@@ -88,8 +39,6 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
ConnectedDeviceGroup (Detector& d, const juce::String& name, PhysicalTopologySource::DeviceConnection* connection) | |||
: detector (d), deviceName (name), deviceConnection (connection) | |||
{ | |||
if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | |||
{ | |||
depreciatedVersionReader = std::make_unique<DepreciatedVersionReader> (*midiDeviceConnection); | |||
@@ -106,30 +55,23 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
sendTopologyRequest(); | |||
} | |||
bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept | |||
~ConnectedDeviceGroup() | |||
{ | |||
return detectedDevices.contains (deviceName) && ! failedToGetTopology(); | |||
for (const auto& device : currentDeviceInfo) | |||
detector.handleDeviceRemoved (device); | |||
} | |||
int getIndexFromDeviceID (Block::UID uid) const noexcept | |||
bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept | |||
{ | |||
for (auto& d : currentDeviceInfo) | |||
if (d.uid == uid) | |||
return d.index; | |||
return -1; | |||
return detectedDevices.contains (deviceName) && ! failedToGetTopology(); | |||
} | |||
const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept | |||
bool contains (Block::UID uid) | |||
{ | |||
for (auto&& status : currentTopologyDevices) | |||
if (getBlockUIDFromSerialNumber (status.serialNumber) == deviceID) | |||
return &status; | |||
return nullptr; | |||
return getIndexFromDeviceID (uid) >= 0; | |||
} | |||
void notifyBlockIsRestarting (Block::UID deviceID) | |||
void handleBlockRestarting (Block::UID deviceID) | |||
{ | |||
forceApiDisconnected (deviceID); | |||
} | |||
@@ -164,63 +106,52 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
{ | |||
lastTopologyReceiveTime = juce::Time::getCurrentTime(); | |||
if (incomingTopologyDevices.isEmpty()) | |||
if (incomingTopologyDevices.isEmpty() | |||
|| incomingTopologyConnections.size() < incomingTopologyDevices.size() - 1) | |||
{ | |||
jassertfalse; | |||
LOG_CONNECTIVITY ("Invalid topology or device list received."); | |||
LOG_CONNECTIVITY ("Device size : " << incomingTopologyDevices.size()); | |||
LOG_CONNECTIVITY ("Connections size : " << incomingTopologyConnections.size()); | |||
scheduleNewTopologyRequest(); | |||
return; | |||
} | |||
currentTopologyDevices.swapWith (incomingTopologyDevices); | |||
currentTopologyConnections.swapWith (incomingTopologyConnections); | |||
incomingTopologyDevices.clearQuick(); | |||
incomingTopologyConnections.clearQuick(); | |||
refreshCurrentDeviceInfo(); | |||
refreshCurrentDeviceConnections(); | |||
removeUnusedBlocksFromMap (versionNumbers, currentDeviceInfo); | |||
removeUnusedBlocksFromMap (blockNames, currentDeviceInfo); | |||
LOG_CONNECTIVITY ("Valid topology received"); | |||
removePingForDisconnectedBlocks(); | |||
detector.handleTopologyChange(); | |||
updateCurrentDeviceList(); | |||
updateCurrentDeviceConnections(); | |||
} | |||
void handleVersion (BlocksProtocol::DeviceVersion version) | |||
{ | |||
const auto uid = getDeviceIDFromIndex (version.index); | |||
if (uid == Block::UID() || version.version.length <= 1) | |||
return; | |||
setVersion (uid, version.version); | |||
setVersion (version.index, version.version); | |||
} | |||
void handleName (BlocksProtocol::DeviceName name) | |||
{ | |||
const auto uid = getDeviceIDFromIndex (name.index); | |||
if (uid == Block::UID() || name.name.length <= 1) | |||
if (name.name.length <= 1) | |||
return; | |||
if (insertOrAssign (blockNames, uid, name.name)) | |||
if (const auto info = getDeviceInfoFromIndex (name.index)) | |||
{ | |||
refreshCurrentDeviceInfo(); | |||
detector.handleTopologyChange(); | |||
if (info->name == name.name) | |||
return; | |||
info->name = name.name; | |||
detector.handleDeviceUpdated (*info); | |||
} | |||
} | |||
void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, | |||
BlocksProtocol::ControlButtonID buttonID, bool isDown) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown); | |||
} | |||
void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data); | |||
} | |||
@@ -231,7 +162,7 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
BlocksProtocol::TouchVelocity velocity, | |||
bool isStart, bool isEnd) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
{ | |||
TouchSurface::Touch touch; | |||
@@ -267,7 +198,7 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
void handlePacketACK (BlocksProtocol::TopologyIndex deviceIndex, | |||
BlocksProtocol::PacketCounter counter) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
{ | |||
detector.handleSharedDataACK (deviceID, counter); | |||
updateApiPing (deviceID); | |||
@@ -278,7 +209,7 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
BlocksProtocol::FirmwareUpdateACKCode resultCode, | |||
BlocksProtocol::FirmwareUpdateACKDetail resultDetail) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
{ | |||
detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get(), (uint32) resultDetail.get()); | |||
updateApiPing (deviceID); | |||
@@ -288,32 +219,32 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
void handleConfigUpdateMessage (BlocksProtocol::TopologyIndex deviceIndex, | |||
int32 item, int32 value, int32 min, int32 max) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
detector.handleConfigUpdateMessage (deviceID, item, value, min, max); | |||
} | |||
void handleConfigSetMessage (BlocksProtocol::TopologyIndex deviceIndex, | |||
int32 item, int32 value) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
detector.handleConfigSetMessage (deviceID, item, value); | |||
} | |||
void handleConfigFactorySyncEndMessage (BlocksProtocol::TopologyIndex deviceIndex) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
detector.handleConfigFactorySyncEndMessage (deviceID); | |||
} | |||
void handleConfigFactorySyncResetMessage (BlocksProtocol::TopologyIndex deviceIndex) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
detector.handleConfigFactorySyncResetMessage (deviceID); | |||
} | |||
void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) | |||
detector.handleLogMessage (deviceID, message); | |||
} | |||
@@ -337,17 +268,14 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
return deviceConnection.get(); | |||
} | |||
juce::Array<DeviceInfo> getCurrentDeviceInfo() | |||
{ | |||
auto blocks = currentDeviceInfo; | |||
blocks.removeIf ([this] (DeviceInfo& info) { return ! isApiConnected (info.uid); }); | |||
return blocks; | |||
} | |||
juce::Array<BlockDeviceConnection> getCurrentDeviceConnections() | |||
{ | |||
auto connections = currentDeviceConnections; | |||
connections.removeIf ([this] (BlockDeviceConnection& c) { return ! isApiConnected (c.device1) || ! isApiConnected (c.device2); }); | |||
juce::Array<BlockDeviceConnection> connections; | |||
for (const auto& connection : currentDeviceConnections) | |||
if (isApiConnected (getDeviceIDFromIndex (connection.device1)) && isApiConnected (getDeviceIDFromIndex (connection.device2))) | |||
connections.add (getBlockDeviceConnection (connection)); | |||
return connections; | |||
} | |||
@@ -359,18 +287,14 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, | |||
private: | |||
//============================================================================== | |||
juce::Array<DeviceInfo> currentDeviceInfo; | |||
juce::Array<BlockDeviceConnection> currentDeviceConnections; | |||
std::unique_ptr<PhysicalTopologySource::DeviceConnection> deviceConnection; | |||
juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices; | |||
juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections, currentDeviceConnections; | |||
juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices, currentTopologyDevices; | |||
juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections, currentTopologyConnections; | |||
std::unique_ptr<PhysicalTopologySource::DeviceConnection> deviceConnection; | |||
juce::CriticalSection incomingPacketLock; | |||
juce::Array<juce::MemoryBlock> incomingPackets; | |||
std::map<Block::UID, BlocksProtocol::VersionNumber> versionNumbers; | |||
std::map<Block::UID, BlocksProtocol::BlockName> blockNames; | |||
std::unique_ptr<DepreciatedVersionReader> depreciatedVersionReader; | |||
struct TouchStart { float x, y; }; | |||
@@ -378,6 +302,21 @@ private: | |||
Block::UID masterBlock = 0; | |||
//============================================================================== | |||
void timerCallback() override | |||
{ | |||
const auto now = juce::Time::getCurrentTime(); | |||
if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0)) | |||
&& now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0) | |||
&& numTopologyRequestsSent < 4) | |||
sendTopologyRequest(); | |||
checkApiTimeouts (now); | |||
startApiModeOnConnectedBlocks(); | |||
requestMasterBlockVersionIfNeeded(); | |||
} | |||
//============================================================================== | |||
void setMidiMessageCallback() | |||
{ | |||
@@ -387,12 +326,57 @@ private: | |||
}; | |||
} | |||
void handleIncomingMessage (const void* data, size_t dataSize) | |||
{ | |||
juce::MemoryBlock mb (data, dataSize); | |||
{ | |||
const juce::ScopedLock sl (incomingPacketLock); | |||
incomingPackets.add (std::move (mb)); | |||
} | |||
triggerAsyncUpdate(); | |||
#if DUMP_BANDWIDTH_STATS | |||
registerBytesIn ((int) dataSize); | |||
#endif | |||
} | |||
void handleAsyncUpdate() override | |||
{ | |||
juce::Array<juce::MemoryBlock> packets; | |||
packets.ensureStorageAllocated (32); | |||
{ | |||
const juce::ScopedLock sl (incomingPacketLock); | |||
incomingPackets.swapWith (packets); | |||
} | |||
for (auto& packet : packets) | |||
{ | |||
auto data = static_cast<const uint8*> (packet.getData()); | |||
BlocksProtocol::HostPacketDecoder<ConnectedDeviceGroup> | |||
::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1); | |||
} | |||
} | |||
bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const | |||
{ | |||
BlocksProtocol::HostPacketBuilder<64> p; | |||
p.writePacketSysexHeaderBytes (deviceIndex); | |||
p.deviceControlMessage (commandID); | |||
p.writePacketSysexFooter(); | |||
return sendMessageToDevice (p); | |||
} | |||
//============================================================================== | |||
juce::Time lastTopologyRequestTime, lastTopologyReceiveTime; | |||
int numTopologyRequestsSent = 0; | |||
void scheduleNewTopologyRequest() | |||
{ | |||
LOG_CONNECTIVITY ("Topology Request Scheduled"); | |||
numTopologyRequestsSent = 0; | |||
lastTopologyReceiveTime = juce::Time(); | |||
lastTopologyRequestTime = juce::Time::getCurrentTime(); | |||
@@ -405,34 +389,11 @@ private: | |||
sendCommandMessage (0, BlocksProtocol::requestTopologyMessage); | |||
} | |||
void timerCallback() override | |||
{ | |||
const auto now = juce::Time::getCurrentTime(); | |||
if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0)) | |||
&& now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0) | |||
&& numTopologyRequestsSent < 4) | |||
sendTopologyRequest(); | |||
checkApiTimeouts (now); | |||
startApiModeOnConnectedBlocks(); | |||
requestMasterBlockVersionIfNeeded(); | |||
} | |||
bool failedToGetTopology() const noexcept | |||
{ | |||
return numTopologyRequestsSent >= 4 && lastTopologyReceiveTime == juce::Time(); | |||
} | |||
bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const | |||
{ | |||
BlocksProtocol::HostPacketBuilder<64> p; | |||
p.writePacketSysexHeaderBytes (deviceIndex); | |||
p.deviceControlMessage (commandID); | |||
p.writePacketSysexFooter(); | |||
return sendMessageToDevice (p); | |||
} | |||
//============================================================================== | |||
void requestMasterBlockVersionIfNeeded() | |||
{ | |||
@@ -442,18 +403,31 @@ private: | |||
const auto masterVersion = depreciatedVersionReader->getVersionNumber(); | |||
if (masterVersion.isNotEmpty()) | |||
setVersion (masterBlock, masterVersion); | |||
{ | |||
const auto masterIndex = getIndexFromDeviceID (masterBlock); | |||
if (masterIndex >= 0) | |||
setVersion (BlocksProtocol::TopologyIndex (masterIndex), masterVersion); | |||
else | |||
jassertfalse; | |||
} | |||
} | |||
void setVersion (const Block::UID uid, const BlocksProtocol::VersionNumber versionNumber) | |||
void setVersion (const BlocksProtocol::TopologyIndex index, const BlocksProtocol::VersionNumber versionNumber) | |||
{ | |||
if (uid == masterBlock) | |||
depreciatedVersionReader.reset(); | |||
if (versionNumber.length <= 1) | |||
return; | |||
if (insertOrAssign (versionNumbers, uid, versionNumber)) | |||
if (const auto info = getDeviceInfoFromIndex (index)) | |||
{ | |||
refreshCurrentDeviceInfo(); | |||
detector.handleTopologyChange(); | |||
if (info->version == versionNumber) | |||
return; | |||
if (info->uid == masterBlock) | |||
depreciatedVersionReader.reset(); | |||
info->version = versionNumber; | |||
detector.handleDeviceUpdated (*info); | |||
} | |||
} | |||
@@ -467,6 +441,31 @@ private: | |||
juce::Array<BlockPingTime> blockPings; | |||
BlockPingTime* getPing (Block::UID uid) | |||
{ | |||
for (auto& ping : blockPings) | |||
if (uid == ping.blockUID) | |||
return &ping; | |||
return nullptr; | |||
} | |||
void removePing (Block::UID uid) | |||
{ | |||
const auto remove = [uid] (const BlockPingTime& ping) | |||
{ | |||
if (uid == ping.blockUID) | |||
{ | |||
LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); | |||
return true; | |||
} | |||
return false; | |||
}; | |||
blockPings.removeIf (remove); | |||
} | |||
void updateApiPing (Block::UID uid) | |||
{ | |||
const auto now = juce::Time::getCurrentTime(); | |||
@@ -480,22 +479,10 @@ private: | |||
{ | |||
LOG_CONNECTIVITY ("API Connected " << uid); | |||
blockPings.add ({ uid, now, now }); | |||
detector.handleTopologyChange(); | |||
} | |||
} | |||
BlockPingTime* getPing (Block::UID uid) | |||
{ | |||
for (auto& ping : blockPings) | |||
if (uid == ping.blockUID) | |||
return &ping; | |||
return nullptr; | |||
} | |||
void removeDeviceInfo (Block::UID uid) | |||
{ | |||
currentDeviceInfo.removeIf ([uid] (DeviceInfo& info) { return uid == info.uid; }); | |||
if (const auto info = getDeviceInfoFromUID (uid)) | |||
detector.handleDeviceAdded (*info); | |||
} | |||
} | |||
bool isApiConnected (Block::UID uid) | |||
@@ -503,55 +490,35 @@ private: | |||
return getPing (uid) != nullptr; | |||
} | |||
void forceApiDisconnected (Block::UID uid) | |||
void forceApiDisconnected (Block::UID /*uid*/) | |||
{ | |||
if (isApiConnected (uid)) | |||
{ | |||
// Clear all known API connections and broadcast an empty topology, | |||
// as DNA blocks connected to the restarting block may be offline. | |||
LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology"); | |||
currentDeviceInfo.clearQuick(); | |||
blockPings.clearQuick(); | |||
blockNames.clear(); | |||
versionNumbers.clear(); | |||
detector.handleTopologyChange(); | |||
scheduleNewTopologyRequest(); | |||
} | |||
} | |||
Array<Block::UID> toRemove; | |||
void checkApiTimeouts (juce::Time now) | |||
{ | |||
const auto timedOut = [this, now] (BlockPingTime& ping) | |||
{ | |||
if (ping.lastPing >= now - juce::RelativeTime::seconds (pingTimeoutSeconds)) | |||
return false; | |||
for (const auto& ping : blockPings) | |||
toRemove.add (ping.blockUID); | |||
LOG_CONNECTIVITY ("Ping timeout: " << ping.blockUID); | |||
removeDeviceInfo (ping.blockUID); | |||
return true; | |||
}; | |||
for (const auto& uid : toRemove) | |||
removeDevice (uid); | |||
if (blockPings.removeIf (timedOut) > 0) | |||
{ | |||
scheduleNewTopologyRequest(); | |||
detector.handleTopologyChange(); | |||
} | |||
scheduleNewTopologyRequest(); | |||
} | |||
/* Returns true is ping was removed */ | |||
void removePingForDisconnectedBlocks() | |||
void checkApiTimeouts (juce::Time now) | |||
{ | |||
const auto removed = [this] (auto& ping) | |||
{ | |||
for (auto& info : currentDeviceInfo) | |||
if (info.uid == ping.blockUID) | |||
return false; | |||
Array<Block::UID> toRemove; | |||
LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); | |||
return true; | |||
}; | |||
for (const auto& ping : blockPings) | |||
{ | |||
if (ping.lastPing < now - juce::RelativeTime::seconds (pingTimeoutSeconds)) | |||
{ | |||
LOG_CONNECTIVITY ("Ping timeout: " << ping.blockUID); | |||
toRemove.add (ping.blockUID); | |||
scheduleNewTopologyRequest(); | |||
} | |||
} | |||
blockPings.removeIf (removed); | |||
for (const auto& uid : toRemove) | |||
removeDevice (uid); | |||
} | |||
void startApiModeOnConnectedBlocks() | |||
@@ -568,151 +535,160 @@ private: | |||
} | |||
//============================================================================== | |||
void checkVersionNumberTimeouts() | |||
Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) noexcept | |||
{ | |||
for (const auto& device : currentDeviceInfo) | |||
{ | |||
const auto version = versionNumbers.find (device.uid); | |||
if (device.index == index) | |||
return device.uid; | |||
if (version == versionNumbers.end()) | |||
{ | |||
auto* ping = getPing (device.uid); | |||
scheduleNewTopologyRequest(); | |||
return {}; | |||
} | |||
} | |||
} | |||
int getIndexFromDeviceID (Block::UID uid) const noexcept | |||
{ | |||
for (auto& d : currentDeviceInfo) | |||
if (d.uid == uid) | |||
return d.index; | |||
return -1; | |||
} | |||
//============================================================================== | |||
Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept | |||
DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept | |||
{ | |||
for (auto& d : currentDeviceInfo) | |||
if (d.index == index) | |||
return d.uid; | |||
if (d.uid == uid) | |||
return &d; | |||
return {}; | |||
return nullptr; | |||
} | |||
Block::UID getDeviceIDFromMessageIndex (BlocksProtocol::TopologyIndex index) noexcept | |||
DeviceInfo* getDeviceInfoFromIndex (BlocksProtocol::TopologyIndex index) const noexcept | |||
{ | |||
const auto uid = getDeviceIDFromIndex (index); | |||
for (auto& d : currentDeviceInfo) | |||
if (d.index == index) | |||
return &d; | |||
// re-request topology if we get an event from an unknown block | |||
if (uid == Block::UID()) | |||
scheduleNewTopologyRequest(); | |||
return nullptr; | |||
} | |||
return uid; | |||
void removeDeviceInfo (Block::UID uid) | |||
{ | |||
currentDeviceInfo.removeIf ([uid] (const DeviceInfo& info) { return info.uid == uid; }); | |||
} | |||
//============================================================================== | |||
void handleIncomingMessage (const void* data, size_t dataSize) | |||
const DeviceStatus* getIncomingDeviceStatus (BlockSerialNumber serialNumber) const | |||
{ | |||
juce::MemoryBlock mb (data, dataSize); | |||
for (auto& device : incomingTopologyDevices) | |||
if (device.serialNumber == serialNumber) | |||
return &device; | |||
{ | |||
const juce::ScopedLock sl (incomingPacketLock); | |||
incomingPackets.add (std::move (mb)); | |||
} | |||
return nullptr; | |||
} | |||
triggerAsyncUpdate(); | |||
//============================================================================== | |||
void removeDevice (Block::UID uid) | |||
{ | |||
if (const auto info = getDeviceInfoFromUID (uid)) | |||
detector.handleDeviceRemoved (*info); | |||
#if DUMP_BANDWIDTH_STATS | |||
registerBytesIn ((int) dataSize); | |||
#endif | |||
removeDeviceInfo (uid); | |||
removePing (uid); | |||
} | |||
void handleAsyncUpdate() override | |||
void updateCurrentDeviceList() | |||
{ | |||
juce::Array<juce::MemoryBlock> packets; | |||
packets.ensureStorageAllocated (32); | |||
Array<Block::UID> toRemove; | |||
//Update known devices | |||
for (int i = currentDeviceInfo.size(); --i >= 0; ) | |||
{ | |||
const juce::ScopedLock sl (incomingPacketLock); | |||
incomingPackets.swapWith (packets); | |||
} | |||
auto& currentDevice = currentDeviceInfo.getReference (i); | |||
for (auto& packet : packets) | |||
{ | |||
auto data = static_cast<const uint8*> (packet.getData()); | |||
if (const auto newStatus = getIncomingDeviceStatus (currentDevice.serial)) | |||
{ | |||
if (currentDevice.index != newStatus->index) | |||
{ | |||
currentDevice.index = newStatus->index; | |||
detector.handleIndexChanged (currentDevice.uid, currentDevice.index); | |||
} | |||
BlocksProtocol::HostPacketDecoder<ConnectedDeviceGroup> | |||
::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1); | |||
} | |||
} | |||
if (currentDevice.batteryCharging != newStatus->batteryCharging) | |||
{ | |||
currentDevice.batteryCharging = newStatus->batteryCharging; | |||
detector.handleBatteryChargingChanged (currentDevice.uid, currentDevice.batteryCharging); | |||
} | |||
//============================================================================== | |||
BlocksProtocol::VersionNumber getVersionNumber (Block::UID uid) | |||
{ | |||
const auto version = versionNumbers.find (uid); | |||
return version == versionNumbers.end() ? BlocksProtocol::VersionNumber() : version->second; | |||
} | |||
if (currentDevice.batteryLevel != newStatus->batteryLevel) | |||
{ | |||
currentDevice.batteryLevel = newStatus->batteryLevel; | |||
detector.handleBatteryLevelChanged (currentDevice.uid, currentDevice.batteryLevel); | |||
} | |||
} | |||
else | |||
{ | |||
toRemove.add (currentDevice.uid); | |||
} | |||
} | |||
BlocksProtocol::BlockName getName (Block::UID uid) | |||
{ | |||
const auto name = blockNames.find (uid); | |||
return name == blockNames.end() ? BlocksProtocol::BlockName() : name->second; | |||
} | |||
for (const auto& uid : toRemove) | |||
removeDevice (uid); | |||
//============================================================================== | |||
const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept | |||
{ | |||
for (auto& d : currentDeviceInfo) | |||
if (d.uid == uid) | |||
return &d; | |||
//Add new devices | |||
for (const auto& device : incomingTopologyDevices) | |||
{ | |||
const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); | |||
return nullptr; | |||
if (! getDeviceInfoFromUID (uid)) | |||
{ | |||
// For backwards compatibility we assume the first device we see in a group is the master and won't change | |||
if (masterBlock == 0) | |||
masterBlock = uid; | |||
currentDeviceInfo.add ({ uid, | |||
device.index, | |||
device.serialNumber, | |||
BlocksProtocol::VersionNumber(), | |||
BlocksProtocol::BlockName(), | |||
device.batteryLevel, | |||
device.batteryCharging, | |||
masterBlock }); | |||
} | |||
} | |||
} | |||
//============================================================================== | |||
Block::ConnectionPort convertConnectionPort (Block::UID uid, BlocksProtocol::ConnectorPort p) noexcept | |||
{ | |||
if (auto* info = getDeviceInfoFromUID (uid)) | |||
return BlocksProtocol::BlockDataSheet (info->serial).convertPortIndexToConnectorPort (p); | |||
jassertfalse; | |||
jassertfalse; | |||
return { Block::ConnectionPort::DeviceEdge::north, 0 }; | |||
} | |||
//============================================================================== | |||
void refreshCurrentDeviceInfo() | |||
BlockDeviceConnection getBlockDeviceConnection (const BlocksProtocol::DeviceConnection& connection) | |||
{ | |||
currentDeviceInfo.clearQuick(); | |||
BlockDeviceConnection dc; | |||
for (auto& device : currentTopologyDevices) | |||
{ | |||
const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); | |||
const auto version = getVersionNumber (uid); | |||
const auto name = getName (uid); | |||
dc.device1 = getDeviceIDFromIndex (connection.device1); | |||
dc.device2 = getDeviceIDFromIndex (connection.device2); | |||
// For backwards compatibility we assume the first device we see in a group is the master and won't change | |||
if (masterBlock == 0) | |||
masterBlock = uid; | |||
if (dc.device1 <= 0 || dc.device2 <= 0) | |||
jassertfalse; | |||
currentDeviceInfo.add ({ uid, | |||
device.index, | |||
device.serialNumber, | |||
version, | |||
name, | |||
masterBlock == uid }); | |||
} | |||
dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, connection.port1); | |||
dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, connection.port2); | |||
return dc; | |||
} | |||
void refreshCurrentDeviceConnections() | |||
void updateCurrentDeviceConnections() | |||
{ | |||
currentDeviceConnections.clearQuick(); | |||
currentDeviceConnections.swapWith (incomingTopologyConnections); | |||
for (auto&& c : currentTopologyConnections) | |||
{ | |||
BlockDeviceConnection dc; | |||
dc.device1 = getDeviceIDFromIndex (c.device1); | |||
dc.device2 = getDeviceIDFromIndex (c.device2); | |||
if (dc.device1 <= 0 || dc.device2 <= 0) | |||
continue; | |||
dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1); | |||
dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2); | |||
currentDeviceConnections.add (dc); | |||
} | |||
detector.handleConnectionsChanged(); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup) | |||
@@ -55,8 +55,8 @@ public: | |||
for (size_t i = 1; i < numFirmwareApps; ++i) | |||
{ | |||
const BlocksVersion highest { asString (highestVersion) }; | |||
const BlocksVersion test { asString ( result[i]) }; | |||
const BlocksVersion highest { highestVersion.asString() }; | |||
const BlocksVersion test { result[i].asString() }; | |||
if (highest < test) | |||
highestVersion = result[i]; | |||
@@ -80,10 +80,21 @@ private: | |||
{ | |||
static constexpr size_t requestSize { 8 }; | |||
static constexpr uint8 requests[][requestSize] = {{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x00, 0xf7 }, // Main App | |||
{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x01, 0xf7 }, // Stm32 | |||
{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x03, 0xf7 }}; // Bootloader | |||
{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x03, 0xf7 }, // Bootloader | |||
{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x01, 0xf7 }}; // Stm32 | |||
deviceConnection.sendMessageToDevice (&requests[currentRequest.get()][0], requestSize); | |||
static const BlocksVersion depreciatedVersion ("0.3.0"); | |||
if (currentRequest.get() == numFirmwareApps - 1 | |||
&& (BlocksVersion (result[0].asString()) >= depreciatedVersion | |||
|| BlocksVersion (result[1].asString()) >= depreciatedVersion)) | |||
{ | |||
stopTimer(); | |||
} | |||
else | |||
{ | |||
deviceConnection.sendMessageToDevice (&requests[currentRequest.get()][0], requestSize); | |||
} | |||
} | |||
//============================================================================== | |||
@@ -23,40 +23,11 @@ | |||
namespace juce | |||
{ | |||
namespace | |||
{ | |||
static bool containsBlockWithUID (const Block::Array& blocks, Block::UID uid) noexcept | |||
{ | |||
for (auto&& block : blocks) | |||
if (block->uid == uid) | |||
return true; | |||
return false; | |||
} | |||
static bool versionNumberChanged (const DeviceInfo& device, juce::String version) noexcept | |||
{ | |||
auto deviceVersion = asString (device.version); | |||
return deviceVersion != version && deviceVersion.isNotEmpty(); | |||
} | |||
static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept | |||
{ | |||
jassert (deviceInfo.uid == block.uid); | |||
block.versionNumber = asString (deviceInfo.version); | |||
} | |||
static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block) | |||
{ | |||
jassert (deviceInfo.uid == block.uid); | |||
block.name = asString (deviceInfo.name); | |||
} | |||
} | |||
//============================================================================== | |||
/** This is the main singleton object that keeps track of connected blocks */ | |||
struct Detector : public juce::ReferenceCountedObject, | |||
private juce::Timer | |||
private juce::Timer, | |||
private juce::AsyncUpdater | |||
{ | |||
using BlockImpl = BlockImplementation<Detector>; | |||
@@ -100,11 +71,10 @@ struct Detector : public juce::ReferenceCountedObject, | |||
if (activeTopologySources.isEmpty()) | |||
{ | |||
for (auto& b : currentTopology.blocks) | |||
if (auto bi = BlockImpl::getFrom (*b)) | |||
if (auto bi = BlockImpl::getFrom (b)) | |||
bi->sendCommandMessage (BlocksProtocol::endAPIMode); | |||
currentTopology = {}; | |||
lastTopology = {}; | |||
auto& d = getDefaultDetectorPointer(); | |||
@@ -124,76 +94,127 @@ struct Detector : public juce::ReferenceCountedObject, | |||
return false; | |||
} | |||
const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept | |||
void handleDeviceAdded (const DeviceInfo& info) | |||
{ | |||
for (auto d : connectedDeviceGroups) | |||
if (auto status = d->getLastStatus (deviceID)) | |||
return status; | |||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||
return nullptr; | |||
} | |||
const auto blockWasRemoved = containsBlockWithUID (blocksToRemove, info.uid); | |||
const auto knownBlock = std::find_if (previouslySeenBlocks.begin(), previouslySeenBlocks.end(), | |||
[uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); | |||
void handleTopologyChange() | |||
{ | |||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||
Block::Ptr block; | |||
if (knownBlock != previouslySeenBlocks.end()) | |||
{ | |||
juce::Array<DeviceInfo> newDeviceInfo; | |||
juce::Array<BlockDeviceConnection> newDeviceConnections; | |||
block = *knownBlock; | |||
for (auto d : connectedDeviceGroups) | |||
if (auto* blockImpl = BlockImpl::getFrom (*block)) | |||
{ | |||
newDeviceInfo.addArray (d->getCurrentDeviceInfo()); | |||
newDeviceConnections.addArray (d->getCurrentDeviceConnections()); | |||
blockImpl->markReconnected (info); | |||
previouslySeenBlocks.removeObject (block); | |||
} | |||
} | |||
else | |||
{ | |||
block = new BlockImpl (*this, info); | |||
} | |||
for (int i = currentTopology.blocks.size(); --i >= 0;) | |||
{ | |||
auto currentBlock = currentTopology.blocks.getUnchecked (i); | |||
currentTopology.blocks.addIfNotAlreadyThere (block); | |||
auto newDeviceIter = std::find_if (newDeviceInfo.begin(), newDeviceInfo.end(), | |||
[&] (DeviceInfo& info) { return info.uid == currentBlock->uid; }); | |||
if (blockWasRemoved) | |||
{ | |||
blocksToUpdate.addIfNotAlreadyThere (block); | |||
blocksToAdd.removeObject (block); | |||
} | |||
else | |||
{ | |||
blocksToAdd.addIfNotAlreadyThere (block); | |||
blocksToUpdate.removeObject (block); | |||
} | |||
auto* blockImpl = BlockImpl::getFrom (*currentBlock); | |||
blocksToRemove.removeObject (block); | |||
if (newDeviceIter == newDeviceInfo.end()) | |||
{ | |||
if (blockImpl != nullptr) | |||
blockImpl->markDisconnected(); | |||
triggerAsyncUpdate(); | |||
} | |||
disconnectedBlocks.addIfNotAlreadyThere (currentTopology.blocks.removeAndReturn (i).get()); | |||
} | |||
else | |||
{ | |||
if (blockImpl != nullptr && blockImpl->wasPowerCycled()) | |||
{ | |||
blockImpl->resetPowerCycleFlag(); | |||
blockImpl->markReconnected (*newDeviceIter); | |||
} | |||
void handleDeviceRemoved (const DeviceInfo& info) | |||
{ | |||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||
updateCurrentBlockInfo (currentBlock, *newDeviceIter); | |||
} | |||
} | |||
const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(), | |||
[uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); | |||
if (blockIt != currentTopology.blocks.end()) | |||
{ | |||
const auto block = *blockIt; | |||
if (auto blockImpl = BlockImpl::getFrom (block)) | |||
blockImpl->markDisconnected(); | |||
currentTopology.blocks.removeObject (block); | |||
previouslySeenBlocks.addIfNotAlreadyThere (block); | |||
blocksToRemove.addIfNotAlreadyThere (block); | |||
blocksToUpdate.removeObject (block); | |||
blocksToAdd.removeObject (block); | |||
triggerAsyncUpdate(); | |||
} | |||
} | |||
static const int maxBlocksToSave = 100; | |||
void handleConnectionsChanged() | |||
{ | |||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||
triggerAsyncUpdate(); | |||
} | |||
void handleDeviceUpdated (const DeviceInfo& info) | |||
{ | |||
if (containsBlockWithUID (blocksToRemove, info.uid)) | |||
return; | |||
if (disconnectedBlocks.size() > maxBlocksToSave) | |||
disconnectedBlocks.removeRange (0, 2 * (disconnectedBlocks.size() - maxBlocksToSave)); | |||
const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(), | |||
[uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); | |||
for (auto& info : newDeviceInfo) | |||
if (info.serial.isValid() && ! containsBlockWithUID (currentTopology.blocks, getBlockUIDFromSerialNumber (info.serial))) | |||
addBlock (info); | |||
if (blockIt != currentTopology.blocks.end()) | |||
{ | |||
const auto block = *blockIt; | |||
if (auto blockImpl = BlockImpl::getFrom (block)) | |||
blockImpl->markReconnected (info); | |||
currentTopology.connections.swapWith (newDeviceConnections); | |||
if (! containsBlockWithUID (blocksToAdd, info.uid)) | |||
{ | |||
blocksToUpdate.addIfNotAlreadyThere (block); | |||
triggerAsyncUpdate(); | |||
} | |||
} | |||
} | |||
void handleBatteryChargingChanged (Block::UID deviceID, const BlocksProtocol::BatteryCharging isCharging) | |||
{ | |||
if (auto block = currentTopology.getBlockWithUID (deviceID)) | |||
if (auto blockImpl = BlockImpl::getFrom (*block)) | |||
blockImpl->batteryCharging = isCharging; | |||
} | |||
void handleBatteryLevelChanged (Block::UID deviceID, const BlocksProtocol::BatteryLevel batteryLevel) | |||
{ | |||
if (auto block = currentTopology.getBlockWithUID (deviceID)) | |||
if (auto blockImpl = BlockImpl::getFrom (*block)) | |||
blockImpl->batteryLevel = batteryLevel; | |||
} | |||
broadcastTopology(); | |||
void handleIndexChanged (Block::UID deviceID, const BlocksProtocol::TopologyIndex index) | |||
{ | |||
if (auto block = currentTopology.getBlockWithUID (deviceID)) | |||
if (auto blockImpl = BlockImpl::getFrom (*block)) | |||
blockImpl->topologyIndex = index; | |||
} | |||
void notifyBlockIsRestarting (Block::UID deviceID) | |||
{ | |||
for (auto& group : connectedDeviceGroups) | |||
group->notifyBlockIsRestarting (deviceID); | |||
group->handleBlockRestarting (deviceID); | |||
} | |||
void handleSharedDataACK (Block::UID deviceID, uint32 packetCounter) const | |||
@@ -305,24 +326,11 @@ struct Detector : public juce::ReferenceCountedObject, | |||
} | |||
//============================================================================== | |||
int getIndexFromDeviceID (Block::UID deviceID) const noexcept | |||
{ | |||
for (auto* c : connectedDeviceGroups) | |||
{ | |||
auto index = c->getIndexFromDeviceID (deviceID); | |||
if (index >= 0) | |||
return index; | |||
} | |||
return -1; | |||
} | |||
template <typename PacketBuilder> | |||
bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const | |||
{ | |||
for (auto* c : connectedDeviceGroups) | |||
if (c->getIndexFromDeviceID (deviceID) >= 0) | |||
if (c->contains (deviceID)) | |||
return c->sendMessageToDevice (builder); | |||
return false; | |||
@@ -341,11 +349,8 @@ struct Detector : public juce::ReferenceCountedObject, | |||
{ | |||
for (const auto& d : connectedDeviceGroups) | |||
{ | |||
for (const auto& info : d->getCurrentDeviceInfo()) | |||
{ | |||
if (info.uid == b.uid) | |||
return d->getDeviceConnection(); | |||
} | |||
if (d->contains (b.uid)) | |||
return d->getDeviceConnection(); | |||
} | |||
return nullptr; | |||
@@ -355,11 +360,8 @@ struct Detector : public juce::ReferenceCountedObject, | |||
{ | |||
for (const auto& d : connectedDeviceGroups) | |||
{ | |||
for (const auto& info : d->getCurrentDeviceInfo()) | |||
{ | |||
if (info.uid == b.uid) | |||
return d->getDeviceConnection(); | |||
} | |||
if (d->contains (b.uid)) | |||
return d->getDeviceConnection(); | |||
} | |||
return nullptr; | |||
@@ -370,10 +372,11 @@ struct Detector : public juce::ReferenceCountedObject, | |||
juce::Array<PhysicalTopologySource*> activeTopologySources; | |||
BlockTopology currentTopology, lastTopology; | |||
juce::ReferenceCountedArray<Block, CriticalSection> disconnectedBlocks; | |||
BlockTopology currentTopology; | |||
private: | |||
Block::Array previouslySeenBlocks, blocksToAdd, blocksToRemove, blocksToUpdate; | |||
void timerCallback() override | |||
{ | |||
startTimer (1500); | |||
@@ -384,21 +387,20 @@ private: | |||
handleDevicesAdded (detectedDevices); | |||
} | |||
void handleDevicesRemoved (const juce::StringArray& detectedDevices) | |||
bool containsBlockWithUID (const juce::Block::Array& blocks, juce::Block::UID uid) | |||
{ | |||
bool anyDevicesRemoved = false; | |||
for (const auto block : blocks) | |||
if (block->uid == uid) | |||
return true; | |||
return false; | |||
} | |||
void handleDevicesRemoved (const juce::StringArray& detectedDevices) | |||
{ | |||
for (int i = connectedDeviceGroups.size(); --i >= 0;) | |||
{ | |||
if (! connectedDeviceGroups.getUnchecked(i)->isStillConnected (detectedDevices)) | |||
{ | |||
connectedDeviceGroups.remove (i); | |||
anyDevicesRemoved = true; | |||
} | |||
} | |||
if (anyDevicesRemoved) | |||
handleTopologyChange(); | |||
} | |||
void handleDevicesAdded (const juce::StringArray& detectedDevices) | |||
@@ -424,57 +426,9 @@ private: | |||
return false; | |||
} | |||
void addBlock (DeviceInfo info) | |||
{ | |||
if (! reactivateBlockIfKnown (info)) | |||
addNewBlock (info); | |||
} | |||
bool reactivateBlockIfKnown (DeviceInfo info) | |||
{ | |||
const auto uid = getBlockUIDFromSerialNumber (info.serial); | |||
for (int i = disconnectedBlocks.size(); --i >= 0;) | |||
{ | |||
if (uid != disconnectedBlocks.getUnchecked (i)->uid) | |||
continue; | |||
auto block = disconnectedBlocks.removeAndReturn (i); | |||
if (auto* blockImpl = BlockImpl::getFrom (*block)) | |||
{ | |||
blockImpl->markReconnected (info); | |||
currentTopology.blocks.add (block); | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
void addNewBlock (DeviceInfo info) | |||
{ | |||
currentTopology.blocks.add (new BlockImpl (info.serial, *this, info.version, | |||
info.name, info.isMaster)); | |||
} | |||
void updateCurrentBlockInfo (Block::Ptr blockToUpdate, DeviceInfo& updatedInfo) | |||
{ | |||
jassert (updatedInfo.uid == blockToUpdate->uid); | |||
if (versionNumberChanged (updatedInfo, blockToUpdate->versionNumber)) | |||
setVersionNumberForBlock (updatedInfo, *blockToUpdate); | |||
if (updatedInfo.name.isNotEmpty()) | |||
setNameForBlock (updatedInfo, *blockToUpdate); | |||
if (updatedInfo.isMaster != blockToUpdate->isMasterBlock()) | |||
BlockImpl::getFrom (*blockToUpdate)->setToMaster (updatedInfo.isMaster); | |||
} | |||
BlockImpl* getBlockImplementationWithUID (Block::UID deviceID) const noexcept | |||
{ | |||
if (auto&& block = currentTopology.getBlockWithUID (deviceID)) | |||
if (auto block = currentTopology.getBlockWithUID (deviceID)) | |||
return BlockImpl::getFrom (*block); | |||
return nullptr; | |||
@@ -484,31 +438,41 @@ private: | |||
//============================================================================== | |||
/** This is a friend of the BlocksImplementation that will scan and set the | |||
physical positions of the blocks */ | |||
struct BlocksTraverser | |||
physical positions of the blocks. | |||
Returns an array of blocks that were updated. | |||
*/ | |||
struct BlocksLayoutTraverser | |||
{ | |||
void traverseBlockArray (const BlockTopology& topology) | |||
static Block::Array updateBlocks (const BlockTopology& topology) | |||
{ | |||
Block::Array updated; | |||
juce::Array<Block::UID> visited; | |||
for (auto& block : topology.blocks) | |||
{ | |||
if (block->isMasterBlock() && ! visited.contains (block->uid)) | |||
{ | |||
if (auto* bi = dynamic_cast<BlockImpl*> (block)) | |||
if (auto* bi = BlockImpl::getFrom (block)) | |||
{ | |||
bi->masterUID = {}; | |||
bi->position = {}; | |||
bi->rotation = 0; | |||
if (bi->rotation != 0 || bi->position.first != 0 || bi->position.second != 0) | |||
{ | |||
bi->rotation = 0; | |||
bi->position = {}; | |||
updated.add (block); | |||
} | |||
} | |||
layoutNeighbours (*block, topology, block->uid, visited); | |||
layoutNeighbours (*block, topology, visited, updated); | |||
} | |||
} | |||
return updated; | |||
} | |||
private: | |||
// returns the distance from corner clockwise | |||
int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index) | |||
static int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index) | |||
{ | |||
if (block->getType() == Block::seaboardBlock) | |||
{ | |||
@@ -533,7 +497,7 @@ private: | |||
} | |||
// returns how often north needs to rotate by 90 degrees | |||
int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge) | |||
static int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge) | |||
{ | |||
switch (edge) | |||
{ | |||
@@ -547,8 +511,10 @@ private: | |||
return 0; | |||
} | |||
void layoutNeighbours (Block::Ptr block, const BlockTopology& topology, | |||
Block::UID masterUid, juce::Array<Block::UID>& visited) | |||
static void layoutNeighbours (const Block::Ptr block, | |||
const BlockTopology& topology, | |||
juce::Array<Block::UID>& visited, | |||
Block::Array& updated) | |||
{ | |||
visited.add (block->uid); | |||
@@ -568,10 +534,17 @@ private: | |||
const auto myOffset = getUnitForIndex (block, myPort.edge, myPort.index); | |||
const auto theirOffset = getUnitForIndex (neighbourPtr, theirPort.edge, theirPort.index); | |||
neighbour->masterUID = masterUid; | |||
neighbour->rotation = (2 + block->getRotation() | |||
+ getRotationForEdge (myPort.edge) | |||
- getRotationForEdge (theirPort.edge)) % 4; | |||
{ | |||
const auto neighbourRotation = (2 + block->getRotation() | |||
+ getRotationForEdge (myPort.edge) | |||
- getRotationForEdge (theirPort.edge)) % 4; | |||
if (neighbour->rotation != neighbourRotation) | |||
{ | |||
neighbour->rotation = neighbourRotation; | |||
updated.addIfNotAlreadyThere (neighbourPtr); | |||
} | |||
} | |||
std::pair<int, int> delta; | |||
const auto theirBounds = neighbour->getBlockAreaWithinLayout(); | |||
@@ -592,10 +565,22 @@ private: | |||
break; | |||
} | |||
neighbour->position = { myBounds.x + delta.first, myBounds.y + delta.second }; | |||
} | |||
{ | |||
const auto neighbourX = myBounds.x + delta.first; | |||
const auto neighbourY = myBounds.y + delta.second; | |||
layoutNeighbours (neighbourPtr, topology, masterUid, visited); | |||
if (neighbour->position.first != neighbourX | |||
|| neighbour->position.second != neighbourY) | |||
{ | |||
neighbour->position.first = neighbourX; | |||
neighbour->position.second = neighbourY; | |||
updated.addIfNotAlreadyThere (neighbourPtr); | |||
} | |||
} | |||
layoutNeighbours (neighbourPtr, topology, visited, updated); | |||
} | |||
} | |||
} | |||
} | |||
@@ -674,22 +659,64 @@ private: | |||
#endif | |||
//============================================================================== | |||
void broadcastTopology() | |||
void updateBlockPositions() | |||
{ | |||
if (currentTopology != lastTopology) | |||
const auto updated = BlocksLayoutTraverser::updateBlocks (currentTopology); | |||
for (const auto block : updated) | |||
{ | |||
lastTopology = currentTopology; | |||
if (containsBlockWithUID (blocksToAdd, block->uid) || containsBlockWithUID (blocksToRemove, block->uid)) | |||
continue; | |||
BlocksTraverser traverser; | |||
traverser.traverseBlockArray (currentTopology); | |||
blocksToUpdate.addIfNotAlreadyThere (block); | |||
} | |||
} | |||
for (auto* d : activeTopologySources) | |||
d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); }); | |||
void updateBlockConnections() | |||
{ | |||
currentTopology.connections.clearQuick(); | |||
for (auto d : connectedDeviceGroups) | |||
currentTopology.connections.addArray (d->getCurrentDeviceConnections()); | |||
} | |||
void handleAsyncUpdate() override | |||
{ | |||
updateBlockConnections(); | |||
updateBlockPositions(); | |||
for (auto* d : activeTopologySources) | |||
{ | |||
for (const auto block : blocksToAdd) | |||
d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockAdded (block); }); | |||
for (const auto block : blocksToRemove) | |||
d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockRemoved (block); }); | |||
for (const auto block : blocksToUpdate) | |||
d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockUpdated (block); }); | |||
} | |||
const auto topologyChanged = blocksToAdd.size() > 0 || blocksToRemove.size() > 0 || blocksToUpdate.size() > 0; | |||
if (topologyChanged) | |||
{ | |||
#if DUMP_TOPOLOGY | |||
dumpTopology (lastTopology); | |||
dumpTopology (currentTopology); | |||
#endif | |||
for (auto* d : activeTopologySources) | |||
d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); }); | |||
} | |||
blocksToUpdate.clear(); | |||
blocksToAdd.clear(); | |||
blocksToRemove.clear(); | |||
static const int maxBlocksToSave = 100; | |||
if (previouslySeenBlocks.size() > maxBlocksToSave) | |||
previouslySeenBlocks.removeRange (0, 2 * (previouslySeenBlocks.size() - maxBlocksToSave)); | |||
} | |||
//============================================================================== | |||
@@ -26,9 +26,13 @@ namespace juce | |||
struct DeviceInfo | |||
{ | |||
// VS2015 requires a constructor to avoid aggregate initialization | |||
DeviceInfo (Block::UID buid, BlocksProtocol::TopologyIndex tidx, BlocksProtocol::BlockSerialNumber s, | |||
BlocksProtocol::VersionNumber v, BlocksProtocol::BlockName n, bool master = false) | |||
: uid (buid), index (tidx), serial (s), version (v), name (n), isMaster (master) | |||
DeviceInfo (Block::UID buid, BlocksProtocol::TopologyIndex tidx, | |||
BlocksProtocol::BlockSerialNumber s, BlocksProtocol::VersionNumber v, | |||
BlocksProtocol::BlockName n, BlocksProtocol::BatteryLevel level, | |||
BlocksProtocol::BatteryCharging charging, Block::UID master) | |||
: uid (buid), index (tidx), serial (s), version (v), name (n), | |||
batteryLevel (level), batteryCharging (charging), masterUid (master), | |||
isMaster (uid == master) | |||
{ | |||
} | |||
@@ -37,6 +41,9 @@ struct DeviceInfo | |||
BlocksProtocol::BlockSerialNumber serial; | |||
BlocksProtocol::VersionNumber version; | |||
BlocksProtocol::BlockName name; | |||
BlocksProtocol::BatteryLevel batteryLevel; | |||
BlocksProtocol::BatteryCharging batteryCharging; | |||
Block::UID masterUid; | |||
bool isMaster {}; | |||
}; | |||
@@ -47,17 +47,6 @@ | |||
#include "internal/juce_BandwidthStatsLogger.cpp" | |||
#endif | |||
namespace | |||
{ | |||
/** Helper function to create juce::String from BlockStringData */ | |||
template <size_t MaxSize> | |||
juce::String asString (juce::BlocksProtocol::BlockStringData<MaxSize> blockString) | |||
{ | |||
return { reinterpret_cast<const char*> (blockString.data), | |||
juce::jmin (sizeof (blockString.data), static_cast<size_t> (blockString.length))}; | |||
} | |||
} | |||
#include "internal/juce_MidiDeviceConnection.cpp" | |||
#include "internal/juce_MIDIDeviceDetector.cpp" | |||
#include "internal/juce_DeviceInfo.cpp" | |||
@@ -48,7 +48,20 @@ public: | |||
struct Listener | |||
{ | |||
virtual ~Listener() = default; | |||
virtual void topologyChanged() = 0; | |||
/** Called for any change in topology - devices changed, connections changed, etc. */ | |||
virtual void topologyChanged() {} | |||
/** Called when a new block is added to the topology. */ | |||
virtual void blockAdded (const Block::Ptr) {} | |||
/** Called when a block is removed from the topology. */ | |||
virtual void blockRemoved (const Block::Ptr) {} | |||
/** Called when a known block is updated. | |||
This could be becasue details have been reveived asyncroniously. E.g. Block name. | |||
*/ | |||
virtual void blockUpdated (const Block::Ptr) {} | |||
}; | |||
void addListener (Listener* l) { listeners.add (l); } | |||