blob: 3f4b14f1eb5fb450d7e91f172b781ec6662f8b6e [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.
#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