blob: 6ce6f14e7e797fcb0668520da30692496540e73b [file] [log] [blame]
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#define PW_LOG_MODULE_NAME "MORSE"
#define PW_LOG_LOG_LEVEL PW_LOG_LEVEL_INFO
#include "modules/morse_code/encoder.h"
#include <cctype>
#include <mutex>
#include "pw_function/function.h"
#include "pw_log/log.h"
namespace sense {
Encoder::~Encoder() { encode_task_.Deregister(); }
void Encoder::Init(pw::async2::Dispatcher& dispatcher,
pw::async2::TimeProvider<pw::chrono::SystemClock>& time,
pw::Allocator& allocator,
OutputFunction&& output) {
dispatcher_ = &dispatcher;
time_ = &time;
allocator_ = &allocator;
output_ = std::move(output);
}
pw::Status Encoder::Encode(std::string_view msg,
uint32_t repeat,
uint32_t interval_ms) {
if (repeat == 0) {
PW_LOG_INFO("Encoding message forever at a %ums interval", interval_ms);
repeat = std::numeric_limits<uint32_t>::max();
} else {
PW_LOG_INFO(
"Encoding message %u times at a %ums interval", repeat, interval_ms);
}
pw::chrono::SystemClock::duration interval =
pw::chrono::SystemClock::for_at_least(
std::chrono::milliseconds(interval_ms));
encode_task_.Deregister();
pw::async2::CoroContext coro_cx(*allocator_);
pw::async2::Coro<pw::Status> coro =
EncodeLoop(coro_cx, msg, repeat, interval);
if (!coro.IsValid()) {
PW_LOG_ERROR("Failed to allocate morse encoder coroutine.");
return pw::Status::ResourceExhausted();
}
encode_task_.SetCoro(std::move(coro));
dispatcher_->Post(encode_task_);
return pw::OkStatus();
}
bool Encoder::IsIdle() const { return !encode_task_.IsRegistered(); }
pw::async2::Coro<pw::Status> Encoder::EncodeLoop(
pw::async2::CoroContext&,
pw::InlineString<kMaxMsgLen> msg,
uint32_t repeat,
pw::chrono::SystemClock::duration interval) {
if (msg.size() == 0 || msg[0] == '\0') {
co_return pw::OkStatus();
}
for (; repeat > 0; --repeat) {
for (uint32_t msg_offset = 0; msg_offset < msg.size(); ++msg_offset) {
char c = msg[msg_offset];
if (c == '\0') {
break;
}
if (isspace(c)) {
// Merge consecutive whitespace characters.
bool prev_was_space = msg_offset != 0 && isspace(msg[msg_offset - 1]);
if (prev_was_space) {
continue;
}
// Words are separated by 7 dits worth of blanks.
// The previous symbol ended with 3 blanks, so add 4 more.
co_await time_->WaitFor(interval * 4);
continue;
}
// Encode the character.
const auto& encoding = ForChar(c);
uint32_t remaining_bits = encoding.bits;
bool is_on = false;
for (uint8_t num_bits = encoding.num_bits; num_bits > 0; --num_bits) {
bool on = (remaining_bits % 2) != 0;
remaining_bits >>= 1;
if (on != is_on) {
Output output;
output.on_ = on;
output.message_finished_ = false;
output_(output);
is_on = on;
}
co_await time_->WaitFor(interval);
}
}
Output output;
output.on_ = false;
output.message_finished_ = true;
output_(output);
}
co_return pw::OkStatus();
}
} // namespace sense