/* Copyright 2020, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
namespace ableton
{
namespace platforms
{
namespace esp32
{
template
class Context
{
class ServiceRunner
{
static void run(void* userParams)
{
auto runner = static_cast(userParams);
for (;;)
{
try
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
runner->mpService->poll_one();
}
catch (...)
{
}
}
}
static void IRAM_ATTR timerIsr(void* userParam)
{
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(*((TaskHandle_t*)userParam), &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken)
{
portYIELD_FROM_ISR();
}
}
public:
ServiceRunner()
: mpService(new ::asio::io_service())
, mpWork(new ::asio::io_service::work(*mpService))
{
xTaskCreatePinnedToCore(run, "link", 8192, this, 2 | portPRIVILEGE_BIT,
&mTaskHandle, LINK_ESP_TASK_CORE_ID);
const esp_timer_create_args_t timerArgs = {
.callback = &timerIsr,
.arg = (void*)&mTaskHandle,
.dispatch_method = ESP_TIMER_TASK,
.name = "link",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timerArgs, &mTimer));
ESP_ERROR_CHECK(esp_timer_start_periodic(mTimer, 100));
}
~ServiceRunner()
{
esp_timer_delete(mTimer);
vTaskDelete(mTaskHandle);
}
template
void async(Handler handler)
{
mpService->post(std::move(handler));
}
::asio::io_service& service() const
{
return *mpService;
}
private:
TaskHandle_t mTaskHandle;
esp_timer_handle_t mTimer;
std::unique_ptr<::asio::io_service> mpService;
std::unique_ptr<::asio::io_service::work> mpWork;
};
public:
using Timer = ::ableton::platforms::asio::AsioTimer;
using Log = LogT;
template
using LockFreeCallbackDispatcher = LockFreeCallbackDispatcher;
template
using Socket = asio::Socket;
Context()
: Context(DefaultHandler{})
{
}
template
explicit Context(ExceptionHandler exceptHandler)
{
}
Context(const Context&) = delete;
Context(Context&& rhs)
: mLog(std::move(rhs.mLog))
, mScanIpIfAddrs(std::move(rhs.mScanIpIfAddrs))
{
}
void stop()
{
}
template
Socket openUnicastSocket(const ::asio::ip::address& addr)
{
auto socket =
addr.is_v4() ? Socket{serviceRunner().service(), ::asio::ip::udp::v4()}
: Socket{serviceRunner().service(), ::asio::ip::udp::v6()};
socket.mpImpl->mSocket.set_option(
::asio::ip::multicast::enable_loopback(addr.is_loopback()));
if (addr.is_v4())
{
socket.mpImpl->mSocket.set_option(
::asio::ip::multicast::outbound_interface(addr.to_v4()));
socket.mpImpl->mSocket.bind(
::LINK_ASIO_NAMESPACE::ip::udp::endpoint{addr.to_v4(), 0});
}
else if (addr.is_v6())
{
const auto scopeId = addr.to_v6().scope_id();
socket.mpImpl->mSocket.set_option(
::asio::ip::multicast::outbound_interface(static_cast(scopeId)));
socket.mpImpl->mSocket.bind(
::LINK_ASIO_NAMESPACE::ip::udp::endpoint{addr.to_v6(), 0});
}
else
{
throw(std::runtime_error("Unknown Protocol"));
}
return socket;
}
template
Socket openMulticastSocket(const ::asio::ip::address& addr)
{
auto socket =
addr.is_v4() ? Socket{serviceRunner().service(), ::asio::ip::udp::v4()}
: Socket{serviceRunner().service(), ::asio::ip::udp::v6()};
socket.mpImpl->mSocket.set_option(::asio::ip::udp::socket::reuse_address(true));
socket.mpImpl->mSocket.set_option(
::asio::socket_base::broadcast(!addr.is_loopback()));
socket.mpImpl->mSocket.set_option(
::asio::ip::multicast::enable_loopback(addr.is_loopback()));
if (addr.is_v4())
{
socket.mpImpl->mSocket.set_option(
::asio::ip::multicast::outbound_interface(addr.to_v4()));
socket.mpImpl->mSocket.bind(
{::asio::ip::address_v4::any(), discovery::multicastEndpointV4().port()});
socket.mpImpl->mSocket.set_option(::asio::ip::multicast::join_group(
discovery::multicastEndpointV4().address().to_v4(), addr.to_v4()));
}
else if (addr.is_v6())
{
const auto scopeId = addr.to_v6().scope_id();
socket.mpImpl->mSocket.set_option(
::asio::ip::multicast::outbound_interface(static_cast(scopeId)));
const auto multicastEndpoint = discovery::multicastEndpointV6(scopeId);
socket.mpImpl->mSocket.bind(
{::asio::ip::address_v6::any(), multicastEndpoint.port()});
socket.mpImpl->mSocket.set_option(
::asio::ip::multicast::join_group(multicastEndpoint.address().to_v6(), scopeId));
}
else
{
throw(std::runtime_error("Unknown Protocol"));
}
return socket;
}
std::vector<::asio::ip::address> scanNetworkInterfaces()
{
return mScanIpIfAddrs();
}
Timer makeTimer() const
{
return {serviceRunner().service()};
}
Log& log()
{
return mLog;
}
template
void async(Handler handler)
{
serviceRunner().service().post(std::move(handler));
}
private:
// Default handler is hidden and defines a hidden exception type
// that will never be thrown by other code, so it effectively does
// not catch.
struct DefaultHandler
{
struct Exception
{
};
void operator()(const Exception&)
{
}
};
static ServiceRunner& serviceRunner()
{
static ServiceRunner runner;
return runner;
}
Log mLog;
ScanIpIfAddrs mScanIpIfAddrs;
};
} // namespace esp32
} // namespace platforms
} // namespace ableton