step: Implementing maintanance logic, preparing production

This commit is contained in:
2026-06-28 01:01:27 +02:00
parent 26c12b7e80
commit 3d878e9a9a
8 changed files with 501 additions and 116 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
# Commands
```sh
# set audio volume
alsamixer
```
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <cstdint>
static constexpr uint16_t UndefCmd = 0xFFFF;
enum ConMode_e {
ModeMqtt,
ModeLoRa
};
enum IncommingCommands_e : uint16_t {
Qurey, // Qurey info
SetMaintanance, // Set Maintanance mode
SetPromoMode, // Enable PromoMode
SetGamingMode, // Enable in Game mode
};
enum ServiceCommands_e {
Open,
Close,
Play1,
Play2,
Play3,
};
enum GameModeCommands_e {
GameNextTryLocked, // Lock sword on next try
GameNextTryRelease // Release sword on next try
};
enum OutgoingCommands_e {
Status, // Send Status
Response, // Confirm last command
Error, // Send error status
PullHappend // Message if User tried to pull the sword
};
enum GameModeResponses_e {
SwordPullHappend, // Message if User tried to pull the sword
SwordReleased, // Message that User pulled the sword
SwordReturned // Message that User returned the sword
};
struct StatusFrame {
uint8_t cmd{OutgoingCommands_e::Status};
uint8_t version[4];
uint8_t mode;
uint8_t healt;
uint8_t sensors;
};
struct ResponseFrame {
uint8_t cmd{OutgoingCommands_e::Response};
uint8_t resCmd;
uint8_t status;
};
struct RxMsg {
RxMsg(uint16_t m)
: code(m) {};
uint16_t code;
};
struct TxMsg {
TxMsg(uint32_t m)
: cmd(m) {};
uint8_t cmd;
};
struct ResponseTxMsg : public TxMsg {
ResponseTxMsg(uint16_t cmd, uint8_t stat)
: TxMsg(OutgoingCommands_e::Response) {};
uint16_t resCmd;
uint8_t status;
};
struct StatusTxMsg : public TxMsg {
StatusTxMsg(uint8_t mode, uint8_t swEnd, uint8_t swBeg, uint8_t swPull, uint8_t out)
: TxMsg(OutgoingCommands_e::Status),
_mode(mode),
_inSwEnd(swEnd),
_inSwBeg(swBeg),
_inSwPull(swPull),
_out(out) {
}
uint8_t _mode;
bool _inSwEnd;
bool _inSwBeg;
bool _inSwPull;
bool _out;
};
+345 -110
View File
@@ -1,101 +1,199 @@
#include "swordMain.hpp" #include "swordMain.hpp"
#include "interface.hpp"
using namespace pal; using namespace pal;
using namespace std::literals::string_view_literals; using namespace std::literals::string_view_literals;
using namespace std::literals::chrono_literals; using namespace std::literals::chrono_literals;
using namespace std;
// using namespace std::literals; // using namespace std::literals;
constexpr int OutPin = 23; const string BrockerUrl = "mqtts://nerdyssey.de:8883";
const string StationPath = "outdoor/king-arthur/sword/";
enum IncommingCommands_e { constexpr uint8_t VersionArr[4] = {'T', 0, 5, 0};
Qurey, // Qurey info const string Version = "T0.5.0";
Disable, // Disable Station
Maintanance, // Set Maintanance mode
SetPromoMode, // Enable PromoMode
SetGamingMode, // Enable in Game mode
NextTryLock, // Lock sword on next try
NextTryRelease // Release sword on next try
};
enum OutgoingCommands_e {
Version, // Send Version
Status, // Send Status
TryHappend, // Message if User tried to pull the sword
SwordReleased, // Message that User pulled the sword
SwordReturned // Message that User returned the sword
};
/// @brief incomming / outgoing message codes constexpr int SwordEntryPin = 5;
struct TxMsg { constexpr int SwordEndPin = 0;
TxMsg(std::uint32_t m) constexpr int SwordPulledPin = 25;
: message(m) {};
uint32_t message;
};
// struct RsrsCtx {
// std::string name;
// Mutex& mtx;
// decltype(std::chrono::steady_clock::duration()) duration;
// };
constexpr int LinMotPin1 = 13;
constexpr int LinMotPin2 = 12;
constexpr int GPIO_RST_PIN = 4;
constexpr int GPIO_DIO0_PIN = 27;
constexpr ConMode_e ConMode{ConMode_e::ModeMqtt};
const string soundPromo = "/home/gustice/workspace/kingArth/swordStation/assets/promo.wav";
const string soundDeny = "/home/gustice/workspace/kingArth/swordStation/assets/deny.wav";
const string soundAllow = "/home/gustice/workspace/kingArth/swordStation/assets/allow.wav";
void playWavFile(const string& filePath) {
if (filePath.empty()) {
fmt::println(stderr, "ERROR: Empty file path");
return;
}
thread audioThread([filePath]() {
string cmd = "aplay \"" + filePath + "\" 2>/dev/null";
int result = system(cmd.c_str());
if (result != 0) {
fmt::println(stderr, "ERROR: Failed to play audio file: {}", filePath);
}
});
audioThread.detach();
}
enum class OpMode {
Undefined, // Enable in Game mode
Maintanance = 1, // Set Maintanance mode
PromoMode, // Enable PromoMode
GamingMode, // Enable in Game mode
};
OpMode Mode = OpMode::Maintanance;
unique_ptr<DeviceIoState> IoStat;
/// @brief MQTT-Service provider /// @brief MQTT-Service provider
// void LoRaServiceTask(Thread::Context& ctx, Queue<TxMsg>& queue) { void MQttServiceTask(Thread::Context& ctx, Queue<RxMsg>& rxQueue, Queue<TxMsg>& txQueue) {
void MQttServiceTask(Thread::Context& ctx) { string runTopic = StationPath + "run";
printf("MQTT service: Starting\n"); string intTopic = StationPath + "int";
// uint32_t cnt{}; string statTopic = StationPath + "stat";
fmt::println("MQTT: Starting task:");
fmt::println(" Topic: {}", runTopic);
auto mqttCallback = [&](string topic, string payload) {
fmt::print("MQTT: on sendTrigger {}:{}'\n", topic, payload);
static std::map<string, IncommingCommands_e> commands = {
{"Qurey", IncommingCommands_e::Qurey},
{"SetMaintanance", IncommingCommands_e::SetMaintanance},
{"SetPromoMode", IncommingCommands_e::SetPromoMode},
{"SetGamingMode", IncommingCommands_e::SetGamingMode},
};
static std::map<string, ServiceCommands_e> serCommands = {
{"ServiceOpen", ServiceCommands_e::Open},
{"ServiceClose", ServiceCommands_e::Close},
{"ServicePlay1", ServiceCommands_e::Play1},
{"ServicePlay2", ServiceCommands_e::Play2},
{"ServicePlay3", ServiceCommands_e::Play3},
};
static std::map<string, GameModeCommands_e> gameCommands = {
{"GameStayLocked", GameModeCommands_e::GameNextTryLocked},
{"GameReleaseLock", GameModeCommands_e::GameNextTryRelease},
};
if (topic != runTopic) {
fmt::print("MQTT: ignore invalid topic {}'\n", topic);
return;
}
if (commands.contains(payload)) {
rxQueue.emplace<RxMsg>(commands.at(payload));
return;
}
if (serCommands.contains(payload)) {
auto code = serCommands.at(payload);
rxQueue.emplace<RxMsg>(code);
return;
}
if (gameCommands.contains(payload)) {
rxQueue.emplace<RxMsg>(serCommands.at(payload));
return;
}
fmt::print("MQTT: unknown command {}:{}'\n", topic, payload);
};
try {
std::vector<string> topicsToRegister{runTopic};
MqttClient client("Sword", BrockerUrl, topicsToRegister);
fmt::println("MQTT: service: successfully connected ...");
client.registerCallback(mqttCallback);
client.send(statTopic, fmt::format("{} Mode={}, sE:0,sS:0,sP:0,lE:0",
Version, (int)Mode));
int cnt = 0;
fmt::println("MQTT: Running ...");
while (!ctx.isCancelled()) { while (!ctx.isCancelled()) {
Thread::sleep(2s); auto tx = txQueue.dequeue();
// blinkSem.give();
// auto e = std::make_unique<TxMsg>(cnt++, "tick"); switch (tx->cmd) {
// queue.enqueue(std::move(e)); case OutgoingCommands_e::Status: {
std::cout << "MQTT service: Running ...\n"; auto& s = *IoStat;
client.send(statTopic, fmt::format("{} Mode={}, sE:{},sS:{},sP:{},lE:{}",
Version, (int)Mode,
s.SwordEnd.isActive(),
s.SwordEntry.isActive(),
s.SwordPulled.isActive(),
s.SwordLatch.isLocked()));
} break;
case OutgoingCommands_e::Response:
client.send(intTopic, "some response");
break;
case OutgoingCommands_e::Error:
client.send(intTopic, "some error");
break;
case OutgoingCommands_e::PullHappend:
client.send(intTopic, "pulled");
break;
default:
break;
}
client.send(intTopic, "sent");
}
} catch (const exception& exc) {
fmt::println(stderr, "\nERROR: Unable to connect to Setup bridge: {}", exc.what());
ctx.markFailed();
} }
} }
/// @brief LoRa-Service provider /// @brief LoRa-Service provider
// void LoRaServiceTask(Thread::Context& ctx, Queue<TxMsg>& queue) { void LoRaServiceTask(Thread::Context& ctx, Queue<RxMsg>& rxQueue, Queue<TxMsg>& txQueue) {
void LoRaServiceTask(Thread::Context& ctx) { fmt::println("LoRa: Starting task:");
printf("LoRa service: Starting\n");
// uint32_t cnt{}; auto loraCallback = [&](std::vector<uint8_t> data) {
auto hex = bytesToHex(data);
auto str = bytesToPrintable(data);
fmt::print("LoRa: Received Byts: {}\n 0x{}\n {}\n", data.size(), hex, str);
auto e = std::make_unique<RxMsg>(0);
rxQueue.enqueue(std::move(e));
return true;
};
auto spi = SpiPort({.mode = SpiPort::mode0,
.cs = SpiPort::Cs0,
.frequ = 1'000'000});
LoRa loRa({
.frequency = 868'000'000,
.port = spi,
.DioPin = GPIO_DIO0_PIN,
.resetPin = GPIO_RST_PIN,
},
loraCallback);
fmt::println("LoRa: Running ...");
while (!ctx.isCancelled()) { while (!ctx.isCancelled()) {
Thread::sleep(2s); auto tx = txQueue.dequeue();
// blinkSem.give(); loRa.send({0});
// auto e = std::make_unique<TxMsg>(cnt++, "tick");
// queue.enqueue(std::move(e));
std::cout << "LoRa service: Running ...\n";
}
// while (!ctx.isCancelled()) {
// auto msg = queue.dequeue(); // Wait with infinit timeout, guaranteed to succeed
// printf ("Message: %s %lu\n", msg->message.c_str(), msg->cnt); // printf ("Message: %s %lu\n", msg->message.c_str(), msg->cnt);
// }
// while (!ctx.isCancelled()) {
// blinkSem.take();
// led.write(true); // execute blink event
// Thread::sleep(100ms);
// led.write(false); // execute blink event
// printf("Blink tick\n");
// }
} }
// void RessourceAccessor(Thread::Context& tCtx, RsrsCtx& ctx) { try {
// using namespace std::chrono; } catch (const exception& exc) {
// fmt::println("starting Ressource Accessor {}", ctx.name); fmt::println(stderr, "\nERROR: Unable to connect to Setup bridge: {}", exc.what());
// while (!tCtx.isCancelled()) { ctx.markFailed();
// { }
// StopWatch sw; }
// ctx.mtx.claim();
// auto e = duration_cast<milliseconds>(sw.getEleapsed());
// auto d = duration_cast<milliseconds>(ctx.duration);
// fmt::println("took {}ms to quire, now access ressource for {}ms",
// (int)e.count(), (int)d.count());
// Thread::sleep(ctx.duration);
// ctx.mtx.release();
// }
// Thread::sleep(100ms);
// }
// }
void itrHandler() { int main(int argc, char* argv[]) {
fmt::println("Sword Firmware");
auto itrHandler = [&]() {
TriggeredInput itr(26, "PItr"); TriggeredInput itr(26, "PItr");
while (true) { while (true) {
std::cout << "Await next interrupt\n"; std::cout << "Await next interrupt\n";
@@ -103,46 +201,183 @@ void itrHandler() {
std::cout << "Interrupt Event\n"; std::cout << "Interrupt Event\n";
} }
} }
} };
int main(int argc, char* argv[]) {
printf("Sword Firmware\n");
OutputPort out(OutPin, "POut");
InputPort in(20, "PIn");
std::cout << "setting GPIO21 as Out and GPIO20 as input: ";
std::thread interruptThread(itrHandler); std::thread interruptThread(itrHandler);
// Semaphore blinkSem; InputPort spPort(SwordPulledPin, "PInSPull");
auto mqttTask = Thread::createExplicit("MqttTask", MQttServiceTask); InputPort sbPort(SwordEntryPin, "PinSEntry");
auto loraTask = Thread::createExplicit("LoRaTask", LoRaServiceTask); InputPort sePort(SwordEndPin, "PInSEnd");
// static Queue<TxMsg> txQueue; OutputPort mOut1(LinMotPin1, "M1:1");
// auto mqttTask = Thread::createExplicit("MqttTask", MQttServiceTask, std::ref(txQueue)); OutputPort mOut2(LinMotPin2, "M1:2");
// auto loraTask = Thread::createExplicit("LoRaTask", LoRaServiceTask, std::ref(txQueue)); InputTransAware SwordEntry(sbPort);
InputTransAware SwordEnd(sePort);
InputTransAware SwordPulled(spPort);
LinMot SwordLatch(mOut1, mOut2);
// Mutex rsrMtx; IoStat = make_unique<DeviceIoState>(SwordEntry, SwordEnd, SwordPulled, SwordLatch);
// RsrsCtx rsCtx1{
// .name = "intense",
// .mtx = rsrMtx,
// .duration = 3000ms};
// RsrsCtx rsCtx2{
// .name = "light",
// .mtx = rsrMtx,
// .duration = 1000ms};
// auto ressourceAccess1 = Thread::createExplicit("IntenseAccess", RessourceAccessor, std::ref(rsCtx1));
// auto ressourceAccess2 = Thread::createExplicit("LightAccess", RessourceAccessor, std::ref(rsCtx2));
fmt::print("Running App\n"); auto tick = [&]() {
SwordEntry.sample();
SwordEnd.sample();
SwordPulled.sample();
};
static Queue<RxMsg> rxQueue;
static Queue<TxMsg> txQueue;
static auto conTask = (ConMode == ModeMqtt) ? Thread::createExplicit("MqttTask", MQttServiceTask, std::ref(rxQueue), std::ref(txQueue))
: Thread::createExplicit("LoRaTask", LoRaServiceTask, std::ref(rxQueue), std::ref(txQueue));
SwordLatch.moveOut();
const static map<IncommingCommands_e, OpMode> opCodes = {
{SetMaintanance, OpMode::Maintanance},
{SetPromoMode, OpMode::PromoMode},
{SetGamingMode, OpMode::GamingMode},
};
fmt::println("Running App");
while (true) {
auto msg = rxQueue.dequeue();
fmt::println("MAIN: Got message {}", msg->code);
auto code = static_cast<IncommingCommands_e>(msg->code);
if (opCodes.contains(code)) {
Mode == opCodes.at(code);
} else {
Mode = OpMode::Undefined;
}
switch (Mode) {
case OpMode::Maintanance: {
fmt::println("Got Maintanance");
auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
// txQueue.emplace<ResponseTxMsg>(msg->code, 0);
while (true) { while (true) {
std::cout << "in=" << in.read() << "\n"; auto serMsg = rxQueue.tryDequeueFor(1000ms);
out.write(0); if (serMsg) {
std::this_thread::sleep_for(std::chrono::milliseconds(500)); auto code = static_cast<ServiceCommands_e>(serMsg->code);
std::cout << "in=" << in.read() << "\n"; switch (code) {
out.write(1); case ServiceCommands_e::Open: {
std::this_thread::sleep_for(std::chrono::milliseconds(500)); IoStat->SwordLatch.moveIn();
// std::this_thread::sleep_for(1s); } break;
case ServiceCommands_e::Close: {
IoStat->SwordLatch.moveOut();
} break;
case ServiceCommands_e::Play1: {
playWavFile(soundPromo);
} break;
case ServiceCommands_e::Play2: {
playWavFile(soundDeny);
} break;
case ServiceCommands_e::Play3: {
playWavFile(soundAllow);
} break;
}
}
auto e = std::make_unique<StatusTxMsg>(
(uint8_t)Mode,
IoStat->SwordEnd.get(),
IoStat->SwordEntry.get(),
IoStat->SwordPulled.get(),
IoStat->SwordLatch.isLocked());
txQueue.enqueue(std::move(e));
}
e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
} break;
case OpMode::PromoMode: {
fmt::println("Got SetPromoMode");
auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
while (true) {
SwordPulled.sample();
auto rx = txQueue.tryDequeueFor(10ms);
if (SwordPulled.isPosEdge()) {
// say: Play our tours
Thread::sleep(5s);
}
}
} break;
case OpMode::GamingMode: {
fmt::println("Got SetGamingMode");
auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
while (true) {
auto gameMsg = txQueue.dequeue();
auto code = static_cast<GameModeCommands_e>(msg->code);
switch (code) {
case GameModeCommands_e::GameNextTryLocked: {
auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
fmt::println("Got NextTryLock");
while (true) {
SwordPulled.sample();
auto rx = txQueue.tryDequeueFor(20ms);
bool pulled = SwordPulled.read();
if (SwordPulled.isPosEdge()) {
// say: You are not the one
Thread::sleep(5s);
}
}
} break;
case GameModeCommands_e::GameNextTryRelease: {
auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
SwordLatch.moveIn();
fmt::println("Got NextTryRelease");
while (true) {
SwordEnd.sample();
auto rx = txQueue.tryDequeueFor(20ms);
if (SwordEnd.isNegEdge()) {
break;
}
}
while (true) {
SwordEntry.sample();
auto rx = txQueue.tryDequeueFor(20ms);
if (SwordEntry.isNegEdge()) {
break;
}
}
// say: The prophecy is fulfilled
e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
while (true) {
SwordEnd.sample();
auto rx = txQueue.tryDequeueFor(20ms);
if (SwordEnd.isNegEdge()) {
break;
}
}
while (true) {
SwordEntry.sample();
auto rx = txQueue.tryDequeueFor(20ms);
if (SwordEntry.isNegEdge()) {
break;
}
}
e = std::make_unique<ResponseTxMsg>(msg->code, 0);
txQueue.enqueue(std::move(e));
} break;
}
}
} break;
default:
break;
}
} }
interruptThread.join(); interruptThread.join();
return EXIT_SUCCESS; return EXIT_SUCCESS;
+55
View File
@@ -12,6 +12,7 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -27,3 +28,57 @@
#include "spiPort.hpp" #include "spiPort.hpp"
#include "textUtils.hpp" #include "textUtils.hpp"
#include "thread.hpp" #include "thread.hpp"
#include "palHw.hpp"
class LinMot {
public:
LinMot(OutputPort& o1, OutputPort& o2)
: _out1(o1),
_out2(o2) {
}
void moveOut() {
_out2.write(false);
_out1.write(true);
_locked = true;
}
void moveIn() {
_out2.write(true);
_out1.write(false);
_locked = false;
}
bool isLocked() {
return _locked;
}
private:
OutputPort& _out1;
OutputPort& _out2;
bool _locked;
};
class DeviceIoState {
public:
DeviceIoState(pal::InputTransAware& entry,
pal::InputTransAware& end,
pal::InputTransAware& pulled,
LinMot& latch)
: SwordEntry(entry),
SwordEnd(end),
SwordPulled(pulled),
SwordLatch(latch) {
}
void tick() {
SwordEntry.sample();
SwordEnd.sample();
SwordPulled.sample();
}
pal::InputTransAware& SwordEntry;
pal::InputTransAware& SwordEnd;
pal::InputTransAware& SwordPulled;
LinMot& SwordLatch;
};