615 lines
26 KiB
C++
615 lines
26 KiB
C++
/**
|
|
* @author Gustice
|
|
* @brief ChaosWatch Application
|
|
*/
|
|
|
|
// using Elite2D-EXMU
|
|
// Gledopto Series With Ethernet
|
|
// { // 13
|
|
// 1, // eth_address,
|
|
// 5, // eth_power,
|
|
// 23, // eth_mdc,
|
|
// 33, // eth_mdio,
|
|
// ETH_PHY_LAN8720, // eth_type,
|
|
// ETH_CLOCK_GPIO0_IN // eth_clk_mode as input
|
|
// },
|
|
//
|
|
// LED-Strips: 14 - 22 - 30
|
|
// >> update http://172.16.16.10:8000/LeoLightShow.bin
|
|
|
|
#include "config.hpp"
|
|
#include "lighsMain.hpp"
|
|
|
|
using namespace spit;
|
|
using namespace std;
|
|
|
|
string ClientName = "LeoLightsDev";
|
|
string ProjectName = "LeoLightsProj";
|
|
gpio_num_t LedPin = gpio_num_t::GPIO_NUM_16;
|
|
gpio_num_t RelayPin = gpio_num_t::GPIO_NUM_18;
|
|
|
|
extern "C" { // This switch allows the ROS C-implementation to find this main
|
|
void app_main(void);
|
|
}
|
|
|
|
struct DeviceState {
|
|
Color color{0x80, 0x80, 0x40}; // Color for the chosen bottle
|
|
vector<uint8_t> grouping; // LED groups for LED-Strip
|
|
vector<uint8_t> times; // Step times for effects
|
|
};
|
|
struct MasterRequest {};
|
|
struct SlaveResponse {};
|
|
static fos::Queue<MasterRequest> commandQ(2);
|
|
static fos::Queue<SlaveResponse> responseQ(2);
|
|
|
|
DeviceState State;
|
|
static const char* TAG = "Light";
|
|
static const char* VERSION = "V 0.7.0";
|
|
unique_ptr<Cli> pShell;
|
|
|
|
void onCommand(string name, Cli::CArgs args) {
|
|
ESP_LOGI(TAG, "Invoking: %s", name.c_str());
|
|
}
|
|
|
|
void setLogServerConfig(Cli::CArgs args) {
|
|
ESP_LOGI(TAG, "on showLogServerConfig:");
|
|
auto& store = NvsApi::getInstance();
|
|
if (args.size() != 1) {
|
|
auto logger = store.getValue("syslogDst");
|
|
pShell->writeLine(string{"syslog destination = '"} + logger + "'");
|
|
pShell->writeLine("provide argument to change syslog server");
|
|
return;
|
|
}
|
|
pShell->writeLine(string{"setting syslog destination to = '"} + args[0] + "'");
|
|
store.setValue("syslogDst", args[0]);
|
|
}
|
|
|
|
void testLights(Cli::CArgs args) {
|
|
ESP_LOGI(TAG, "on testLights:");
|
|
commandQ.enqueue(make_unique<MasterRequest>());
|
|
}
|
|
|
|
void showProcessValues(Cli::CArgs args) {
|
|
pShell->writeLine("Process values:");
|
|
char buff[256];
|
|
|
|
sprintf(buff, "%02x,%02x,%02x", State.color.getRed(), State.color.getGreen(), State.color.getBlue());
|
|
std::stringstream ssC;
|
|
for (auto& c : State.grouping) {
|
|
ssC << string{c} << ",";
|
|
}
|
|
std::stringstream ssT;
|
|
for (auto& t : State.times) {
|
|
ssT << string{t} << ",";
|
|
}
|
|
pShell->writeLine(string{" Color RGB: "} + buff);
|
|
pShell->writeLine(string{" LED-Counts: "} + ssC.str());
|
|
pShell->writeLine(string{" Switch-Times: "} + ssT.str());
|
|
}
|
|
|
|
/**
|
|
* @brief Telnet socket via TCP/IP
|
|
*
|
|
* @details The ini-file for TeraTerm only changes CRReceive and CRSend values both to "=CR"
|
|
* @param arg ignored task argument
|
|
*/
|
|
static void telnetSocketTask(int addrFamily) {
|
|
ESP_LOGI(TAG, "Starting Telnet task");
|
|
try {
|
|
TelnetServer telnet(addrFamily);
|
|
telnet.run(
|
|
[](TcpSession& session) {
|
|
pShell = make_unique<Cli>(session, [](string n, Cli::CArgs a) { onCommand(n, a); });
|
|
pShell->addBinding(
|
|
{"setLogserver", "Set log server config ip",
|
|
[](Cli::CArgs a) { setLogServerConfig(a); }});
|
|
|
|
Syslog::registerCli(*pShell);
|
|
auto& store = NvsApi::getInstance();
|
|
store.registerCli(*pShell);
|
|
SystemFunctions::registerCli(*pShell);
|
|
|
|
pShell->addBinding(
|
|
{"setDyn", "set dynamics of movement (velocity,accelerations) as floats",
|
|
[](Cli::CArgs a) { testLights(a); }});
|
|
pShell->addBinding(
|
|
{"showData", "Show process values that are input by DMX, MQTT and SPIT",
|
|
[](Cli::CArgs a) { showProcessValues(a); }});
|
|
},
|
|
[](TcpSession& session) {
|
|
pShell->process();
|
|
Syslog::setupTask(session.getOther());
|
|
});
|
|
} catch (const exception& e) {
|
|
ESP_LOGE("TCP", "%s", e.what());
|
|
}
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void startShow(string data) {
|
|
ESP_LOGI(TAG, "StartShuffle command %s", data.c_str());
|
|
try {
|
|
Syslog::logv(Syslog::Severity::Informational, "got Shuffle-Command");
|
|
commandQ.enqueue(make_unique<MasterRequest>());
|
|
} catch (const exception& e) {
|
|
ESP_LOGE(TAG, "Error to process data %s, with ex: %s", data.c_str(), e.what());
|
|
}
|
|
}
|
|
|
|
constexpr uint32_t DmxUniverse = 0;
|
|
constexpr int DmxChannelCount = 9;
|
|
array<uint8_t, 512> LedData;
|
|
|
|
static void UniverseCallback(const uint8_t* data, const uint16_t size) {
|
|
ESP_LOGD(TAG, "Got DMX");
|
|
ESP_LOG_BUFFER_HEXDUMP("DMX", data, size, ESP_LOG_DEBUG);
|
|
Syslog::logv(Syslog::Severity::Debug, "Got DMX: r%0x g%0x b%0x i%0x ...",
|
|
/* RGB*/ data[0], data[1], data[2], /*Intens*/ data[3]);
|
|
|
|
// DMX 512
|
|
for (int i = 0; i < LedData.size() / 4;) {
|
|
LedData[i] = data[i];
|
|
i++;
|
|
LedData[i] = data[i];
|
|
i++;
|
|
LedData[i] = data[i];
|
|
i++;
|
|
LedData[i] = data[i];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief DMX-Socket for ArtNet TCP/IP
|
|
*
|
|
* @details Tested with [Q Light Controller+](https://www.qlcplus.org/)
|
|
* @param args ignored task argument
|
|
*/
|
|
static void dmxSocketTask(int addrFamily) {
|
|
ESP_LOGI(TAG, "Starting ArtNet task");
|
|
constexpr uint16_t ArtNetPort = arx::artnet::DEFAULT_PORT;
|
|
IpAdr address = addrFamily == AF_INET ? IpAdr(INADDR_ANY, ArtNetPort) : IpAdr(ArtNetPort);
|
|
|
|
try {
|
|
while (true) {
|
|
UdpSocket socket(address);
|
|
socket.bindTo();
|
|
|
|
ArtnetReceiver artnet(socket);
|
|
artnet.subscribe(DmxUniverse, UniverseCallback);
|
|
artnet.parse();
|
|
// parse artnet as long socket is active, then restart socket
|
|
while (socket.isActive()) {
|
|
ESP_LOGD(TAG, "Waiting for data");
|
|
artnet.parse();
|
|
}
|
|
}
|
|
|
|
} catch (const std::exception& e) {
|
|
ESP_LOGE("UDP", "%s", e.what());
|
|
}
|
|
}
|
|
|
|
fos::Semaphore etherActive;
|
|
fos::Semaphore spitProjectFinish;
|
|
|
|
/** Event handler for IP_EVENT_ETH_GOT_IP */
|
|
void got_ip_event_handler(void* arg, esp_event_base_t event_base,
|
|
int32_t event_id, void* event_data) {
|
|
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
|
|
const esp_netif_ip_info_t* ip_info = &event->ip_info;
|
|
|
|
ESP_LOGI(TAG, "Ethernet Got IP Address");
|
|
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
|
ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
|
|
ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
|
|
ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
|
|
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
|
etherActive.give();
|
|
}
|
|
|
|
static void initialize_ethernet(void) {
|
|
uint8_t eth_port_cnt = 0;
|
|
esp_eth_handle_t* eth_handles;
|
|
ESP_ERROR_CHECK(eth_init(ð_handles, ð_port_cnt));
|
|
if (eth_port_cnt > 1) {
|
|
ESP_LOGW(TAG, "multiple Ethernet devices detected, the first initialized is to be used!");
|
|
}
|
|
|
|
static esp_eth_handle_t s_eth_handle = NULL;
|
|
s_eth_handle = eth_handles[0];
|
|
free(eth_handles);
|
|
|
|
// Initialize TCP/IP network interface aka the esp-netif (should be called only once in application)
|
|
ESP_ERROR_CHECK(esp_netif_init());
|
|
// Create default event loop that running in background
|
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
|
|
|
// Use ESP_NETIF_DEFAULT_ETH when just one Ethernet interface is used and you don't need to modify
|
|
// default esp-netif configuration parameters.
|
|
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
|
|
esp_netif_t* eth_netif = esp_netif_new(&cfg);
|
|
// Attach Ethernet driver to TCP/IP stack
|
|
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(s_eth_handle)));
|
|
|
|
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
|
|
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
|
|
ESP_ERROR_CHECK(esp_eth_start(s_eth_handle));
|
|
}
|
|
|
|
namespace Effect {
|
|
extern const WaveForm_t OffsetPulseWave{gau8_offsetPulse, cu16_TemplateLength};
|
|
extern const WaveForm_t InitSlopeWave{gau8_initSlope, cu16_TemplateLength};
|
|
extern const WaveForm_t OffsetSlopeWave{gau8_offsetSlope, cu16_TemplateLength};
|
|
extern const WaveForm_t FullSlopeWave{gau8_fullSlope, cu16_TemplateLength};
|
|
extern const WaveForm_t FullPulseWave{gau8_fullPulse, cu16_TemplateLength};
|
|
|
|
const Macro macDemoStart = {
|
|
{64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
|
{32, 1, StateMachine::UpdateIdle, 0xFF},
|
|
};
|
|
|
|
const Macro macDemoPulse = {
|
|
{64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
|
{32, 2, StateMachine::UpdateIdle, 0xFF},
|
|
{64, 1, &OffsetPulseWave, StateMachine::UpdateWave, 0xFF},
|
|
};
|
|
|
|
const Macro macDemoFlicker = {
|
|
{16, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
|
{32, 1, StateMachine::UpdateFlicker, 0xFF},
|
|
};
|
|
} // namespace Effect
|
|
|
|
void LightingTask() {
|
|
ESP_LOGI(TAG, "Start Lighting");
|
|
auto const& colorSource = State.color;
|
|
|
|
AddressableLeRtz strip(LedPin, AddressableLeRtz::Type::Rgb, LedData.size() / 4);
|
|
OutputPort relay(RelayPin);
|
|
relay.write(true);
|
|
|
|
while (true) {
|
|
for (int i = 0; i < (LedData.size() / 4); i++) {
|
|
auto* c = &LedData[i * 4];
|
|
Color C{c[0], c[1], c[2], c[3]};
|
|
strip.set(C, i, 1);
|
|
}
|
|
strip.send(); // takes ca. 5ms to send all the data
|
|
fos::Task::delayMs(20);
|
|
}
|
|
}
|
|
|
|
struct LocalSpitObj {
|
|
string iD;
|
|
string groupName;
|
|
string name;
|
|
};
|
|
map<string, MasterRequest> ProvidedSpitActions;
|
|
map<string, string> ExpectedObjects;
|
|
|
|
void onServerEvent(SClient::ServerEvent event, const SadDetectedServer& p_DetectedServer) {
|
|
switch (event) {
|
|
case SClient::ServerEvent::Detect:
|
|
ESP_LOGI(TAG, "SERVER DETECTED");
|
|
ESP_LOGI(TAG, " Name: %s", p_DetectedServer.getName().data());
|
|
ESP_LOGI(TAG, " ID: %s", p_DetectedServer.getID().data());
|
|
ESP_LOGI(TAG, " Computer: %s", p_DetectedServer.getComputerName().data());
|
|
ESP_LOGI(TAG, " IP: %s", p_DetectedServer.getIP().data());
|
|
ESP_LOGI(TAG, " Port: %s", p_DetectedServer.getPort().data());
|
|
ESP_LOGI(TAG, " Addition: %s", p_DetectedServer.getAdditions().data());
|
|
ESP_LOGI(TAG, " ClientIP: %s", p_DetectedServer.getClientIP().data());
|
|
break;
|
|
case SClient::ServerEvent::Lost:
|
|
ESP_LOGW(TAG, "Server lost Name: %s IP: %s", p_DetectedServer.getName().data(), p_DetectedServer.getIP().data());
|
|
|
|
break;
|
|
case SClient::ServerEvent::Changed:
|
|
ESP_LOGW(TAG, "Server lost Name: %s IP: %s", p_DetectedServer.getName().data(), p_DetectedServer.getIP().data());
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void onConnectionStatus(string p_ServerID, string p_ServerComputerName, string p_ServerName, int p_ServerStepTime,
|
|
SClient::Connection p_ConnectionStatus, SClient::Connection p_OldConnectionStatus) {
|
|
ESP_LOGI(TAG, "MySPITApp: eventConnectionStatus %d->%d ", (int)p_OldConnectionStatus, (int)p_ConnectionStatus);
|
|
}
|
|
|
|
// The transfer of the server's SPITTypes and SPITObjects is finished.
|
|
void onProjectUpdate(SClient::ProjUpdate state, string projectId) {
|
|
switch (state) {
|
|
case SClient::ProjUpdate::ServerLoaded:
|
|
ESP_LOGI(TAG, "eventProjectReport ID: %s ", projectId.data());
|
|
break;
|
|
case SClient::ProjUpdate::StartTransfer:
|
|
ESP_LOGI(TAG, "eventStartProjectTransfer");
|
|
break;
|
|
case SClient::ProjUpdate::EndTransfer:
|
|
ESP_LOGI(TAG, "eventEndProjectTransfer");
|
|
spitProjectFinish.give();
|
|
break;
|
|
case SClient::ProjUpdate::ServerClosed:
|
|
ESP_LOGI(TAG, "eventProjectClosed ID: %s", projectId.data());
|
|
// If we don't save SPITServer's project specific information
|
|
// perhaps close all SPITObjects and RemoteSPITObjects to avoid conflicts, when the SPITServer loads a new project
|
|
// SPITObject::clearSPITObjects();
|
|
// SPITObject::clearSPITObjectsRemote();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool onCheckSpitTypeVeto(bool knownType, bool createdByClient) {
|
|
return true; // Bypass veto
|
|
if (!knownType || !createdByClient) { // Don't allow foreign SPITTypes
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool onCheckSpitObjectVeto(bool knownType) {
|
|
return true; // Bypass veto
|
|
if (!knownType) { // Don't allow objects of unregistered SPITTypes
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Called when the server has created, loaded, changed or deleted a (Remote) SPITType.
|
|
// Remote SPITType are used in Remote SPITObjects. A Remote SPITObject is used to remote control the SPITServer.
|
|
// May also occur when the server confirms a SPITType created or changed by the client.
|
|
void onSpitType(SClient::ObjectEvent event, bool isRemote, const SType& sType) {
|
|
if (isRemote) {
|
|
if (event == SClient::ObjectEvent::Created) {
|
|
ESP_LOGI(TAG, "NEW SPIT-REMOTE-Type ID: %s GroupName: %s Name: %s confirmed: %d", sType.getID().data(), sType.GroupName.get().data(), sType.Name.get().data(), sType.getConfirmed());
|
|
auto typeName = sType.Name.get();
|
|
if (ExpectedObjects.contains(typeName)) {
|
|
ExpectedObjects[typeName] = sType.getID();
|
|
}
|
|
} else if (event == SClient::ObjectEvent::Changed) {
|
|
// we had to inform all Remote SPITObjects using the p_SPITType
|
|
// perhaps the SPITObject uses information from the SPITType
|
|
// SPITObject *ptrSPITObject;
|
|
// int intSPITObjectCount;
|
|
// intSPITObjectCount = SPITObject::getSPITObjectRemoteCount();
|
|
// for (int intObject = 0; intObject < intSPITObjectCount; intObject++) {
|
|
// ptrSPITObject = SPITObject::getSPITObjectRemote(intObject);
|
|
// if (ptrSPITObject == nullptr)
|
|
// continue;
|
|
// if (ptrSPITObject->getSPITTypeID() == p_SPITType.getID()) {
|
|
// // call ecentSPITObjectRemote
|
|
// // onSpitObjectRemote(SClient::ObjectEvent::Changed, p_SPITType, *ptrSPITObject);
|
|
// }
|
|
// }
|
|
// ESP_LOGI(TAG, "SPIT-REMOTE-Type CHANGED ID: %s GroupName: %s Name: %s confirmed: %d", sType.getID().data(), sType.GroupName.get().data(), sType.Name.get().data(), sType.getConfirmed());
|
|
} else if (event == SClient::ObjectEvent::Deleted) {
|
|
ESP_LOGD(TAG, "SPIT-REMOTE-Type Removed ID: %s GroupName: %s Name: %s\n", sType.getID().data(), sType.GroupName.get().data(), sType.Name.get().data());
|
|
}
|
|
} else {
|
|
if (event == SClient::ObjectEvent::Created) {
|
|
// Whenever you provide an action for this SPITType take this over in your code.
|
|
// call _SpitClient.setActionSPITType(sType.getID(), true);
|
|
// ESP_LOGI(TAG, "NEW SPIT-Type ID: %s GroupName: %s Name: %s confirmed: %d", sType.getID().data(), sType.GroupName.get().data(), sType.Name.get().data(), sType.getConfirmed());
|
|
} else if (event == SClient::ObjectEvent::Changed) {
|
|
// ESP_LOGI(TAG, "SPIT-Type CHANGED ID: %s GroupName: %s Name: %s confirmed: %d", sType.getID().data(), sType.GroupName.get().data(), sType.Name.get().data(), sType.getConfirmed());
|
|
} else if (event == SClient::ObjectEvent::Deleted) {
|
|
// ESP_LOGI(TAG, "SPIT-Type Removed ID: %s GroupName: %s Name: %s\n", sType.getID().data(), sType.GroupName.get().data(), sType.Name.get().data());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Called when the server has created, loaded or changed a SPITObject.
|
|
// A Remote SPITObject is used to remote control the SPITServer.
|
|
// May also occur when the server confirms a SPITObject created by the client, what the client should never do!!.
|
|
// false = server changed a existing SPITObject
|
|
// @param p_SPITType a reference to a copy of the associated SPITType instance.
|
|
// @param p_SPITObject a reference to a copy of the SPITObject instance.
|
|
void onSpitObject(SClient::ObjectEvent event, bool isRemote, const SType& sType, const SObject& sObject) {
|
|
if (isRemote) {
|
|
if (event == SClient::ObjectEvent::Created) {
|
|
// ESP_LOGI(TAG, "NEW SPIT-REMOTE-Object ID: %s Name: %s -> SPITType: %s", sObject.getID().data(), sObject.Name.get().data(), sType.Name.get().data());
|
|
// This is a remote SPITObject which can be used to remote control the server.
|
|
// If you want you can list this for later use
|
|
} else if (event == SClient::ObjectEvent::Changed) {
|
|
// ESP_LOGI(TAG, "SPIT-REMOTE-Object CHANGED ID: %s Name: %s -> SPITType: %s", sObject.getID().data(), sObject.Name.get().data(), sType.Name.get().data());
|
|
} else if (event == SClient::ObjectEvent::Deleted) {
|
|
ESP_LOGD(TAG, "SPIT-REMOTE-Object Removed ID: %s Name: %s\n", sObject.getID().data(), sObject.Name.get().data());
|
|
// This is a remote SPITObject
|
|
// If you have listet this prior you should remove it from the list
|
|
}
|
|
} else {
|
|
if (event == SClient::ObjectEvent::Created) {
|
|
// ESP_LOGI(TAG, "NEW SPIT-Object ID: %s Name: %s -> SPITType: %s", sObject.getID().data(), sObject.Name.get().data(), sType.Name.get().data());
|
|
// check your actions
|
|
// Whenever you provide an action for this SPITObject take this over in your code.
|
|
// call _SpitClient.setActionSPITObject(sObject.getID(), true);
|
|
|
|
// for our little excample as described in loadClientProject:
|
|
// We have only actions for SPITTypes - means every SPITObject with the associated "Actionx"-SPITTypes will trigger the action when it is played
|
|
//-> no need to do something here, because setActionSPITType has been set prior and the SPITObject will automatically inherits this
|
|
// SPITObjects with other SPITTypes asscciated will trigger no actions
|
|
|
|
} else if (event == SClient::ObjectEvent::Changed) {
|
|
// ESP_LOGI(TAG, "SPIT-Object CHANGED ID: %s Name: %s -> SPITType: %s", sObject.getID().data(), sObject.Name.get().data(), sType.Name.get().data());
|
|
} else if (event == SClient::ObjectEvent::Deleted) {
|
|
ESP_LOGD(TAG, "SPIT-Object Removed ID: %s Name: %s\n", sObject.getID().data(), sObject.Name.get().data());
|
|
// Check your actions
|
|
// take the removal of the SPITObject over in your code
|
|
|
|
// CAUTION: Deleted SPITObjects can be added later again (perhaps per UNDO on the SPITServer)
|
|
//-> don't remove your assigned action immediately - keep it to reassign it later
|
|
}
|
|
}
|
|
}
|
|
|
|
// SPITServer may use several methods for playing SPITObjects.
|
|
// e.g. liveSHOWsoftware has the timeline and jingles which plays objects, where the timeline the main player is.
|
|
// SPIT distinguishes between the different players. All eventPlay... functions contain the information which player plays the SPIT object.
|
|
void onPlay(string p_PlayerID, string p_PlayerName, char p_PlayerType, bool p_PlayerIsRunning, uint64_t p_TimeFrames) {
|
|
ESP_LOGD(TAG, "eventPlay Player: %s Runs: %d Time: %lld", p_PlayerName.data(), p_PlayerIsRunning, p_TimeFrames);
|
|
|
|
switch (p_PlayerType) {
|
|
case SClient::PlayerType::PTYPE_MAIN:
|
|
// p_FrameTimes contains the position inside the server's project, we can use it like a timecode
|
|
// int intMilliseconds = (int)(((double)p_TimeFrames / (double)m_ServerFramesPerSecond) * 1000);
|
|
break;
|
|
case SClient::PlayerType::PTYPE_CONTINOUS:
|
|
// The olayer is playing only single Object sequences sometime during the project
|
|
break;
|
|
case SClient::PlayerType::PTYPE_DISCRETE:
|
|
// Only discrete values where submitted, e.g the fader position of remote faders.
|
|
break;
|
|
}
|
|
}
|
|
|
|
void onPlaySpitType(string p_PlayerID, string spitTypeId, bool p_PlayerIsRunning, bool p_Started, bool p_Ended, double p_Value) {
|
|
// double doubleValue = ((double)p_PlayObject.getValue() / 1000000) * ((double)p_PlayObject.getValueFactor() / 1000000);
|
|
ESP_LOGD(TAG, "eventPlayObject Player: %s is running: %d Object Started: %d Stopped: %d", p_PlayerID.data(), p_PlayerIsRunning, p_Started, p_Ended);
|
|
// ESP_LOGD(TAG, " SPITType: %s SPITObject: %s Position: %lld Value: %f\n", spitTypeId.data(), p_SPITObject.getName().data(), p_PlayObject.getFrameInside(), doubleValue);
|
|
ESP_LOGD(TAG, " SPITType: %s Value: %f\n", spitTypeId.data(), p_Value);
|
|
|
|
if (!ProvidedSpitActions.contains(spitTypeId)) {
|
|
ESP_LOGE(TAG, "SPIT ID %s is not accepted", spitTypeId.c_str());
|
|
return;
|
|
}
|
|
if (p_Started == true) {
|
|
auto command = ProvidedSpitActions[spitTypeId];
|
|
// ESP_LOGI(TAG, "Player: %s is running: %s Object", p_PlayerID.data(), spitTypeId.c_str());
|
|
commandQ.enqueue(make_unique<MasterRequest>());
|
|
Syslog::logv(Syslog::Severity::Informational, "Got SPIT command");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a remote SpitObject which value function is changed by the server.
|
|
* This allows the Client to adjust its display for the remote command.
|
|
* @param p_SPITType the assigned remote SPITType of p_SPITObject
|
|
* @param p_SPITObject the remote SPITObject which is changed
|
|
* @param p_FrameInsideObject can be discarded
|
|
* @param p_Value the new value
|
|
*/
|
|
void onPlayRemoteObjectStatus(const SType& sType, const SObject& sObject, uint64_t frameInsideObject, int value) {
|
|
ESP_EARLY_LOGD(TAG, "eventPlayRemoteObjectStatus RemoteSPITType: %s RemoteSPITObject: %s Position: %lld Value: %d", sType.Name.get().data(), sObject.Name.get().data(), frameInsideObject, value);
|
|
}
|
|
|
|
void SpitClientApp() {
|
|
ESP_LOGI(TAG, "Starting SPIT task");
|
|
|
|
auto& provided = SpitTypes.Provided;
|
|
auto& expected = SpitTypes.Expected;
|
|
|
|
vector<LocalSpitObj> ProvidedObj;
|
|
ProvidedSpitActions[provided.group] = MasterRequest{};
|
|
ProvidedObj.push_back(LocalSpitObj{.iD = provided.group, .groupName = provided.group, .name = provided.group});
|
|
ProvidedSpitActions[provided.startShow] = MasterRequest{};
|
|
ProvidedObj.push_back(LocalSpitObj{.iD = provided.startShow, .groupName = provided.group, .name = provided.startShow});
|
|
|
|
ExpectedObjects[expected.showDone] = "";
|
|
|
|
auto ip = spit::Socket::getAdaptersInfo();
|
|
spit::Socket socket(ip);
|
|
SClient spitClient(ClientName, ProjectName, socket,
|
|
{
|
|
.onServerEvent = onServerEvent,
|
|
.onConnectionStatus = onConnectionStatus,
|
|
.onProjectUpdate = onProjectUpdate,
|
|
.onSpitType = onSpitType,
|
|
.onSpitObject = onSpitObject,
|
|
.onPlay = onPlay,
|
|
.onPlaySpitType = onPlaySpitType,
|
|
.onPlayRemoteObjectStatus = onPlayRemoteObjectStatus,
|
|
.onCheckSpitTypeVeto = onCheckSpitTypeVeto,
|
|
.onCheckSpitObjectVeto = onCheckSpitObjectVeto,
|
|
});
|
|
|
|
for (auto& obj : ProvidedObj) {
|
|
SType* ptrSPITType1Fix = spitClient.createTypeFixed(obj.iD, obj.groupName, obj.name, 0, 0);
|
|
spitClient.setActionType(ptrSPITType1Fix->getID(), true); // should be executed whenever a SPITObject with this SPITType is played
|
|
}
|
|
spitClient.clientProjectLoaded();
|
|
spitClient.startServerAutoDetect();
|
|
|
|
spitProjectFinish.take();
|
|
string serverProjectID = spitClient.getServerProjectID();
|
|
string serverProjectName = spitClient.getClientProjectName();
|
|
int serverFramesPerSecond = spitClient.getServerFramesPerSecond(); // set when the server reports his project, see eventProjectReport
|
|
ESP_LOGI(TAG, "eventProjectReport ID: %s Name: %s FramesPerSecond: %d", serverProjectID.data(), serverProjectName.data(), serverFramesPerSecond);
|
|
|
|
for (auto&& obj : ExpectedObjects) {
|
|
if (obj.second == "") {
|
|
ESP_LOGE(TAG, "No matching Remote ID for %s", obj.first.c_str());
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
auto res = responseQ.dequeue();
|
|
spitClient.sendRemoteObject(ExpectedObjects[expected.showDone]);
|
|
}
|
|
}
|
|
|
|
void MqttThread() {
|
|
ESP_LOGI(TAG, "Starting MQTT task");
|
|
|
|
auto& store = NvsApi::getInstance();
|
|
auto broker = store.getValue(ConfigKeys.mqttBroker);
|
|
if (broker != "") {
|
|
ESP_LOGI(TAG, "Initializing MQTT");
|
|
auto prefix = store.getValue(ConfigKeys.mqttPrefix);
|
|
auto& client = MqttClient::init(broker, prefix);
|
|
if (!client.isRunning()) {
|
|
client.addSubscription(MqttTopics.startShow, [](string d) {
|
|
commandQ.enqueue(make_unique<MasterRequest>());
|
|
});
|
|
client.start();
|
|
}
|
|
|
|
while (true) {
|
|
auto res = responseQ.dequeue();
|
|
client.publish(MqttTopics.showDone, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
void app_main(void) {
|
|
initialize_ethernet();
|
|
NvsApi::init();
|
|
static fos::Task lightingTask(LightingTask, "Lights", 0x8000uL);
|
|
|
|
etherActive.take();
|
|
ESP_LOGI(TAG, "Start net services");
|
|
|
|
static int ipVersion = AF_INET;
|
|
static fos::TaskT<int> telnetSocket(telnetSocketTask, ipVersion, "tcpServer", 4096);
|
|
// static fos::Task spit(SpitClientApp, "SpitClient");
|
|
// static fos::Task mqtt(MqttThread, "MqttEndpoint");
|
|
static fos::TaskT<int> dmxNode(dmxSocketTask, ipVersion, "dmxNode", 4096);
|
|
|
|
auto& store = NvsApi::getInstance();
|
|
auto logger = store.getValue(ConfigKeys.syslogDest);
|
|
if (logger != "") {
|
|
Syslog::setupTask(IpAdr{logger, 0});
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Configuration");
|
|
ESP_LOGI(TAG, "%s: %s", ConfigKeys.syslogDest, store.getValue(ConfigKeys.syslogDest).c_str());
|
|
// ESP_LOGI(TAG, "%s: %s", ConfigKeys.mqttBroker, store.getValue(ConfigKeys.mqttBroker).c_str());
|
|
// ESP_LOGI(TAG, "%s: %s", ConfigKeys.mqttPrefix, store.getValue(ConfigKeys.mqttPrefix).c_str());
|
|
// ESP_LOGI(TAG, "%s: %s", ConfigKeys.spitServer, store.getValue(ConfigKeys.spitServer).c_str());
|
|
|
|
ESP_LOGI(TAG, "## Starting : %s : %s",
|
|
CONFIG_LWIP_LOCAL_HOSTNAME, VERSION);
|
|
|
|
while (true) {
|
|
fos::Task::delayMs(5000);
|
|
Syslog::logv(Syslog::Severity::Notice, "Min heap size: %u / FreeHeap %lu",
|
|
heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT),
|
|
esp_get_free_heap_size());
|
|
}
|
|
}
|