| // 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. |
| #pragma once |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <string_view> |
| |
| #include "modules/morse_code/morse_code.rpc.pb.h" |
| #include "modules/worker/worker.h" |
| #include "pw_async2/coro.h" |
| #include "pw_async2/coro_or_else_task.h" |
| #include "pw_async2/dispatcher.h" |
| #include "pw_async2/time_provider.h" |
| #include "pw_chrono/system_clock.h" |
| #include "pw_containers/flat_map.h" |
| #include "pw_status/status.h" |
| #include "pw_string/string.h" |
| |
| namespace sense { |
| namespace internal { |
| |
| struct Encoding { |
| uint32_t bits = 0; |
| |
| // Letters are separated by 3 dits worth of blanks. |
| // The symbol will always end with 1 blank, so add 2 more. |
| uint8_t num_bits = 2; |
| |
| constexpr explicit Encoding(const char* s) { Encode(s); } |
| |
| private: |
| /// Converts a string of "dits" and "dahs", i.e. '.' and '-' respectively, |
| /// into a bit sequence of ons and offs. |
| constexpr void Encode(const char* s) { |
| if (*s == '.') { |
| bits |= (0x1 << num_bits); |
| num_bits += 2; |
| Encode(s + 1); |
| } else if (*s == '-') { |
| bits |= (0x7 << num_bits); |
| num_bits += 4; |
| Encode(s + 1); |
| } |
| } |
| }; |
| |
| // clang-format off |
| constexpr pw::containers::FlatMap<char, Encoding, 38> kEncodings({{ |
| {'A', Encoding(".-") }, {'T', Encoding("-") }, |
| {'B', Encoding("-...") }, {'U', Encoding("..-") }, |
| {'C', Encoding("-.-.") }, {'V', Encoding("...-") }, |
| {'D', Encoding("-..") }, {'W', Encoding(".--") }, |
| {'E', Encoding(".") }, {'X', Encoding("-..-") }, |
| {'F', Encoding("..-.") }, {'Y', Encoding("-.--") }, |
| {'G', Encoding("--.") }, {'Z', Encoding("--..") }, |
| {'H', Encoding("....") }, {'0', Encoding("-----") }, |
| {'I', Encoding("..") }, {'1', Encoding(".----") }, |
| {'J', Encoding(".---") }, {'2', Encoding("..---") }, |
| {'K', Encoding("-.-") }, {'3', Encoding("...--") }, |
| {'L', Encoding(".-..") }, {'4', Encoding("....-") }, |
| {'M', Encoding("--") }, {'5', Encoding(".....") }, |
| {'N', Encoding("-.") }, {'6', Encoding("-....") }, |
| {'O', Encoding("---") }, {'7', Encoding("--...") }, |
| {'P', Encoding(".--.") }, {'8', Encoding("---..") }, |
| {'Q', Encoding("--.-") }, {'9', Encoding("----.") }, |
| {'R', Encoding(".-.") }, {'?', Encoding("..--..") }, |
| {'S', Encoding("...") }, {'@', Encoding(".--.-.") }, |
| }}); |
| // clang-format on |
| |
| } // namespace internal |
| |
| class Encoder final { |
| public: |
| static constexpr size_t kMaxMsgLen = sizeof(morse_code_SendRequest::msg); |
| static constexpr uint32_t kDefaultIntervalMs = 60; |
| static constexpr pw::chrono::SystemClock::duration kDefaultInterval = |
| pw::chrono::SystemClock::for_at_least( |
| std::chrono::milliseconds(kDefaultIntervalMs)); |
| |
| static constexpr const internal::Encoding& ForChar(char c) { |
| auto it = internal::kEncodings.find(toupper(c)); |
| if (it == internal::kEncodings.end()) { |
| it = internal::kEncodings.find('?'); |
| } |
| return it->second; |
| } |
| |
| /// Output of the encoder. Passed to each `OutputFunction` call. |
| class Output { |
| public: |
| /// Whether or not the LED is on. |
| constexpr bool on() const { return on_; } |
| |
| /// True if this is the last LED toggle of the encoded phrase. If the |
| /// encoder is repeating, this is true at the end of each repeated |
| /// message. |
| constexpr bool message_finished() const { return message_finished_; }; |
| |
| private: |
| friend class Encoder; |
| bool on_; |
| bool message_finished_; |
| }; |
| |
| using OutputFunction = pw::Function<void(const Output& output)>; |
| |
| Encoder() |
| : encode_task_(pw::async2::Coro<pw::Status>::Empty(), [](pw::Status) { |
| PW_LOG_ERROR("Failed to allocate morse encode coroutine."); |
| }) {} |
| |
| ~Encoder(); |
| |
| /// Injects this object's dependencies. |
| /// |
| /// This method MUST be called before using any other method. |
| /// |
| /// All reference arguments (the dispatcher, timer, and allocator) are |
| /// stored and used throughout the lifetime of the ``Encoder``, and |
| /// therefore the objects they reference must outlive this ``Encoder``. |
| void Init(pw::async2::Dispatcher& dispatcher, |
| pw::async2::TimeProvider<pw::chrono::SystemClock>& time, |
| pw::Allocator& allocator, |
| OutputFunction&& output); |
| |
| /// Queues a sequence of callbacks to emit the given message in Morse code. |
| /// |
| /// The message is emitted through alternating ON and OFF (true/false) calls |
| /// to the `OutputFunction` provided to `Init`. The shortest ON interval is a |
| /// "dit". The longest is a "dah", which is three times the interval for a |
| /// "dit". The output is kept off for one "dit" interval between each symbol, |
| /// three "dit" intervals between each letter, and 7 "dit" intervals between |
| /// each word. |
| /// |
| /// @param request Message to emit in Morse code. |
| /// @param repeat Number of times to repeat the message; 0 is forever |
| /// @param interval_ms Duration of a "dit" in milliseconds. |
| pw::Status Encode(std::string_view request, |
| uint32_t repeat, |
| uint32_t interval_ms = kDefaultIntervalMs); |
| |
| /// Returns whether this instance is currently emitting a message or not. |
| bool IsIdle() const; |
| |
| private: |
| pw::async2::Coro<pw::Status> EncodeLoop( |
| pw::async2::CoroContext&, |
| pw::InlineString<kMaxMsgLen> msg, |
| uint32_t repeat, |
| pw::chrono::SystemClock::duration interval); |
| |
| pw::async2::Dispatcher* dispatcher_ = nullptr; |
| pw::async2::TimeProvider<pw::chrono::SystemClock>* time_ = nullptr; |
| pw::Allocator* allocator_ = nullptr; |
| OutputFunction output_; |
| |
| pw::async2::CoroOrElseTask encode_task_; |
| }; |
| |
| } // namespace sense |