| // 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(); | |
| ~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 |