| // 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 |