diff --git a/applications/hub_test/Makefile b/applications/hub_test/Makefile new file mode 100644 index 000000000..f5cedde67 --- /dev/null +++ b/applications/hub_test/Makefile @@ -0,0 +1,3 @@ +SUBDIRS = targets +-include config.mk +include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/hub_test/README.md b/applications/hub_test/README.md new file mode 100644 index 000000000..781d7e1e5 --- /dev/null +++ b/applications/hub_test/README.md @@ -0,0 +1,36 @@ +Hub throughput testing application {#hub_test_application} +================================== + +[TOC] + +# Goal + +The purpose of Hub testing is to verify an OpenLCB link or bus for qualitative +and quantitative performance characteristics. These are as follows. + +Qualitative: +- The link has to be lossless. +- There is only limited reordering allowed within the link. +- There has to be proper throttling for ingress of packets to the link. + +Quantitative: +- What is the maximum throughut of packets that can be accepted on the link. +- What is the round-trip-time of the link. +- What is the jitter on the timing of the packet round-trips. + +For hubs, a relevant question is also how do these metrics change as the number +of clients on the hub are increased. + +## Testing methodology + +The application contains a load generator, and a receiver. For a given test, +one generator and one or more receivers have to be attached to the same bus. + +The generator supplies a given load to the bus. This load may be a fixes +traffic, or the maximum traffic that the bus is willing to accept. + +The receiver watches for the traffic coming from the generator, verifies the +qualitative attributes, and measures the quantitative attributes. Statistics +about this are printed to the screen. + + diff --git a/applications/hub_test/config.mk b/applications/hub_test/config.mk new file mode 120000 index 000000000..e270c0389 --- /dev/null +++ b/applications/hub_test/config.mk @@ -0,0 +1 @@ +../default_config.mk \ No newline at end of file diff --git a/applications/hub_test/subdirs b/applications/hub_test/subdirs new file mode 100644 index 000000000..4e0254829 --- /dev/null +++ b/applications/hub_test/subdirs @@ -0,0 +1,2 @@ +SUBDIRS = \ + diff --git a/applications/hub_test/targets/Makefile b/applications/hub_test/targets/Makefile new file mode 100644 index 000000000..0f44f2c8a --- /dev/null +++ b/applications/hub_test/targets/Makefile @@ -0,0 +1,4 @@ +SUBDIRS = \ + linux.x86 \ + +include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/hub_test/targets/linux.x86/.gitignore b/applications/hub_test/targets/linux.x86/.gitignore new file mode 100644 index 000000000..67effd68d --- /dev/null +++ b/applications/hub_test/targets/linux.x86/.gitignore @@ -0,0 +1 @@ +hub_test diff --git a/applications/hub_test/targets/linux.x86/Makefile b/applications/hub_test/targets/linux.x86/Makefile new file mode 100644 index 000000000..c6386dac3 --- /dev/null +++ b/applications/hub_test/targets/linux.x86/Makefile @@ -0,0 +1,2 @@ +-include ../../config.mk +include $(OPENMRNPATH)/etc/prog.mk diff --git a/applications/hub_test/targets/linux.x86/main.cxx b/applications/hub_test/targets/linux.x86/main.cxx new file mode 100644 index 000000000..18206ff6c --- /dev/null +++ b/applications/hub_test/targets/linux.x86/main.cxx @@ -0,0 +1,693 @@ +/** \copyright + * Copyright (c) 2023, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file main.cxx + * + * Main file for the hub tester application. + * + * @author Balazs Racz + * @date 23 Dec 2023 + */ + +#include + +#include "nmranet_config.h" +#include "os/os.h" +#include "os/sleep.h" + +#include "utils/Buffer.hxx" +#include "utils/ClientConnection.hxx" +#include "utils/LimitedPool.hxx" +#include "utils/StringPrintf.hxx" +#include "utils/Stats.hxx" +#include "openlcb/Convert.hxx" + + +OVERRIDE_CONST(gc_generate_newlines, 1); +OVERRIDE_CONST(gridconnect_bridge_max_outgoing_packets, 2); +//OVERRIDE_CONST(gridconnect_tcp_snd_buffer_size, 8192); +//OVERRIDE_CONST(gridconnect_tcp_rcv_buffer_size, 8192); +OVERRIDE_CONST(gridconnect_tcp_notsent_lowat_buffer_size, 1460); + +// Maximum 100 TCP packets per second. +//OVERRIDE_CONST(gridconnect_buffer_delay_usec, 10000); +OVERRIDE_CONST(gridconnect_buffer_delay_usec, 100); +// Or one full packet. +OVERRIDE_CONST(gridconnect_buffer_size, 1460); + +Executor<1> g_executor("g_executor", 0, 1024); +Service g_service(&g_executor); + +class Receiver; + +struct Link +{ + /// Config (cmdline): device file to use + const char *device_path = nullptr; + /// Config (cmdline) TCP target port number + int upstream_port = 12021; + /// Config (cmdline) TCP target hostname + const char *upstream_host = nullptr; + /// The CAN hub for this link. + std::unique_ptr can_hub {new CanHubFlow(&g_service)}; + /// The receiver object keeping track of the inputs and stats. Will be + /// created for every link, but only used for the input ports. + std::unique_ptr receiver; +}; + +/// Defines which packet type we should send to the output interface. +enum class PacketType { + /// Send an event report, listen for the same message coming back. + EVENT_REPORT, + /// Send an Identify Consumer, listen for Consumer Identified + /// valid/invalid/unknown. + EVENT_IDENTIFY, + /// Ping: send an addressed Verify Node ID, listen for Node ID Verified. + NODE_ID_VERIFY, +}; + +/// We generate packets to this interface. +Link output_port; +/// Traffic to generate, default is to saturate the link. +int pkt_per_sec = -1; +/// Which packet to send/expect. +PacketType pkt_type = PacketType::EVENT_REPORT; +/// If we are using an addressed ping, which node should we ping. +openlcb::NodeID destination_nodeid; + +/// We use this special value in the "upstream port" argument to denote a +/// receiver link that should be a loopback. +static constexpr int LOOPBACK_PORT_NUMBER = -999; + +/// How many pending buffers we can allow for the send flow. +static constexpr unsigned SEND_PARALLELISM = 10; + +/// The output frames will go wioth this CAN ID. +static constexpr uint32_t SEND_HEADER_EVENT = 0x195b4ffe; +static constexpr uint32_t SEND_HEADER_IDENT = 0x198f4ffe; +static constexpr uint32_t SEND_HEADER_NODEID = 0x19490ffe; +/// The response frames come with the CAN ID (masked). +static constexpr uint32_t RECV_HEADER_IDENT = 0x194c7fff; +static constexpr uint32_t RECV_MASK_IDENT = ~0x00003fff; +static constexpr uint32_t RECV_HEADER_NODEID = 0x19170fff; +static constexpr uint32_t RECV_MASK_NODEID = ~0x00000fff; + +/// The output frames will go with this CAN data bytes. This is NMRA ID 1, +/// which is not assigned. Lower four bytes are the id. +static constexpr uint8_t SEND_PAYLOAD[8] = {0x9, 0x0, 0x01, 0x39, 0, 0, 0, 0}; + +/// Temporary variable used during args parsing, to hold the -Q value until the +/// next -U comes. +int in_upstream_port = 12021; +/// All the input ports to listen to for incoming traffic. +std::vector input_ports; + +/// All network connections (both input and outputs). +vector> connections; + +/// Used for error printing the usage. +const char *arg0 = "hub_test"; + +// Dynamic variables tracking the traffic. + +/// How many live links are there (upstream + downstream). +int g_num_live_links = 1; + +/// Index of the next packet to generate. +uint32_t g_next_packet = 1; + +/// How many buffers have been sent and waiting for the notification on them. +unsigned g_pending_buffers = 0; + +/// How many packets we have sent to the output iface. +unsigned g_num_packets_sent = 0; +/// How many sent packets got their (send) barrier notified. +unsigned g_num_packets_accepted = 0; +/// How many sent packets got their (receive) barrier notified and removed from +/// storage. +unsigned g_num_packets_all_received = 0; + +void usage(const char *e) +{ + fprintf(stderr, + "Usage: %s (-d device_path | [-q upstream_port] -u upstream_host) [-s " + "speed]\n\t(-D device_path | [-Q upstream_port] -U " + "upstream_host) [-N repeat] [-i | -n node_id]...\n\n", + e); + fprintf(stderr, + "\tdevice_path is a path to a physical device doing " + "serial-CAN or USB-CAN.\n"); + fprintf(stderr, + "\tupstream_host is the host name for an upstream " + "hub.\n"); + fprintf( + stderr, "\tupstream_port is the port number for the upstream hub.\n"); + fprintf(stderr, + "\t-d -q -u specifies the output port, -D -Q -U specifies input ports. " + "-Q must be before -U. Multiple input ports can be specified.\n"); + fprintf(stderr, + "\t-s speed is the packets/sec to generate. Set to -1 for automatic " + "(saturation).\n"); + fprintf(stderr, + "\t-N repeat will open this many input ports with the last settings.\n"); + fprintf(stderr, + "\t-i selects using event identify packets to test node response latency.\n"); + fprintf(stderr, + "\t-n node_id selects using verify node id global packets to test node response latency. These packets are not ordered. Node id should be like 0x0501010118FF\n"); + fprintf(stderr, + "\t-l adds a loopback receiver. This makes sense with -i or -n packet types.\n"); + exit(1); +} + +void parse_args(int argc, char *argv[]) +{ + int opt; + while ((opt = getopt(argc, argv, "hd:u:q:s:D:U:Q:in:N:l")) >= 0) + { + switch (opt) + { + case 'h': + usage(argv[0]); + break; + case 'd': + output_port.device_path = optarg; + break; + case 'u': + output_port.upstream_host = optarg; + break; + case 'q': + output_port.upstream_port = atoi(optarg); + break; + case 'D': + input_ports.emplace_back(); + input_ports.back().device_path = optarg; + break; + case 'U': + input_ports.emplace_back(); + input_ports.back().upstream_host = optarg; + input_ports.back().upstream_port = in_upstream_port; + break; + case 'l': + input_ports.emplace_back(); + input_ports.back().upstream_port = LOOPBACK_PORT_NUMBER; + break; + case 'N': + { + int rept = strtol(optarg, nullptr, 0); + unsigned ref = input_ports.size() - 1; + for (int idx = 0; idx < rept - 1; ++idx) + { + if (!input_ports[ref].upstream_host) + { + DIE("-N multiplicity can not be used with device link " + "(-D /dev/ttyXXX)"); + } + input_ports.emplace_back(); + input_ports.back().upstream_host = input_ports[ref].upstream_host; + input_ports.back().upstream_port = input_ports[ref].upstream_port; + } + break; + } + case 'Q': + in_upstream_port = atoi(optarg); + break; + case 's': + pkt_per_sec = atoi(optarg); + break; + case 'i': + pkt_type = PacketType::EVENT_IDENTIFY; + break; + case 'n': + pkt_type = PacketType::NODE_ID_VERIFY; + destination_nodeid = strtoll(optarg, nullptr, 16); + break; + default: + fprintf(stderr, "Unknown option %c\n", opt); + usage(argv[0]); + } + } +} + +/// Call this function when a packet has all its receivers confirmed. +/// @param index packet number. +void packet_complete(unsigned index); + +struct PacketInfo : private Notifiable +{ + unsigned index_; + + /// Timestamp when the timer ticked to send this packet. + long long timerTs_ {0}; + /// Timestamp when the flow started working on this packet. + long long flowTs_ {0}; + /// Timestamp when the buffer was handed over to the output hub. + long long sendTs_ {0}; + /// Timestamp when the notifiable on the buffer triggered. + long long confirmTs_ {0}; + + /// Send frame notification barrier. + BarrierNotifiable bn_ {this}; + + /// Number of receivers that have not yet gotten this message. + unsigned pendingReceivers_ {0}; + + /// Notifies that this packet was received by a receiver link. + void notify_reception() + { + if (pendingReceivers_) + { + --pendingReceivers_; + } + if (!pendingReceivers_) + { + // Confirmed by all receivers. Removes from storage. + g_num_packets_all_received++; + packet_complete(index_); + } + } + +private: + void notify() override + { + confirmTs_ = os_get_time_monotonic(); + g_pending_buffers--; + g_num_packets_accepted++; + } +}; + +std::map g_packet_data; + +class Receiver : public CanHubPortInterface +{ +public: + Receiver(CanHubFlow *hub) + { + hub->register_port(this); + } + + // This function handles an incoming CAN frame. + void send(message_type *buf, unsigned prio) override + { + auto ts = os_get_time_monotonic(); + auto rb = get_buffer_deleter(buf); + const struct can_frame &f = buf->data()->frame(); + if (!IS_CAN_FRAME_EFF(f)) + { + // not interesting frame + ++numUnknownFrames_; + return; + } + auto id = GET_CAN_FRAME_ID_EFF(f); + uint32_t cnt = 0; + bool skip = true; + switch(pkt_type) { + case PacketType::EVENT_REPORT: { + if (id != SEND_HEADER_EVENT) { + break; + } + if (f.can_dlc != 8 || memcmp(f.data, SEND_PAYLOAD, 4) != 0) + { + break; + } + memcpy(&cnt, f.data + 4, 4); + cnt = be32toh(cnt); + skip = false; + break; + } + case PacketType::EVENT_IDENTIFY: { + if ((id & RECV_MASK_IDENT) != + (RECV_HEADER_IDENT & RECV_MASK_IDENT)) + { + break; + } + if (f.can_dlc != 8 || memcmp(f.data, SEND_PAYLOAD, 4) != 0) + { + break; + } + memcpy(&cnt, f.data + 4, 4); + cnt = be32toh(cnt); + skip = false; + break; + } + case PacketType::NODE_ID_VERIFY: { + if ((id & RECV_MASK_NODEID) != + (RECV_HEADER_NODEID & RECV_MASK_NODEID)) + { + LOG(VERBOSE, "response with wrong header %x", id); + break; + } + openlcb::NodeID actual = openlcb::data_to_node_id(f.data); + if (actual != destination_nodeid) { + + LOG(INFO, "response with wrong node id %012" PRIx64 " vs %012" PRIx64, actual, destination_nodeid); + break; + } + // This packet type does not carry a counter. We just assume + // this is the next correct packet. + cnt = nextPacket_; + skip = false; + break; + } + } + if (skip) + { + // not interesting frame + ++numUnknownFrames_; + return; + } + ++numFrames_; + if (cnt == nextPacket_) + { + ++numInOrder_; + ++nextPacket_; + } + else if (cnt > nextPacket_) + { + numMissed_ = cnt - nextPacket_; + nextPacket_ = cnt + 1; + } + else + { + numOutOfOrder_++; + // we don't adjust next packet here + } + auto it = g_packet_data.find(cnt); + if (it != g_packet_data.end()) + { + long long rtt_usec = NSEC_TO_USEC(ts - it->second.confirmTs_); + rttUsec_.add(rtt_usec); + it->second.notify_reception(); + } + } + + /// Gets a stats line for this input. + string get_stats() + { + string ret; + ret += StringPrintf("\tReceiver: +%d recv, +%d unk, +%d OK, +%d " + "out-of-order, +%d missing", + numFrames_, numUnknownFrames_, numInOrder_, numOutOfOrder_, + numMissed_); + numFrames_ = numUnknownFrames_ = numInOrder_ = numOutOfOrder_ = + numMissed_ = 0; + + ret += StringPrintf("|RTT %s\n", rttUsec_.debug_string().c_str()); + + rttUsec_.clear(); + return ret; + } + +public: + /// Number of incoming frames that we don't recognize. + unsigned numUnknownFrames_ = 0; + /// Number of successful frames received. + unsigned numFrames_ = 0; + /// Number of in-order frames. + unsigned numInOrder_ = 0; + /// Number of frames missed. These may be lost or will arrive later out of + /// order. + unsigned numMissed_ = 0; + /// Number of frames that arrived late / out of order. + unsigned numOutOfOrder_ = 0; + +private: + /// Which packet index are we expecting next. + uint32_t nextPacket_ = 1; + /// Statistics about the RTT of the packet (from send-confirm to + /// receive). + Stats rttUsec_; +}; + +/// Establishes a new connection. +void add_link(Link *link, bool is_receiver) +{ + static int id = 0; + if (link->upstream_host) + { + connections.emplace_back( + new UpstreamConnectionClient(StringPrintf("upstream%d", id), + link->can_hub.get(), link->upstream_host, link->upstream_port)); + } + else if (link->device_path) + { + connections.emplace_back( + new DeviceConnectionClient(StringPrintf("device%d", id), + link->can_hub.get(), link->device_path)); + } + else if (link->upstream_port == LOOPBACK_PORT_NUMBER) + { + // Loopback port is connected to the can hub of the output. + link->receiver.reset(new Receiver(output_port.can_hub.get())); + is_receiver = false; + } + else + { + usage(arg0); + } + if (is_receiver) + { + link->receiver.reset(new Receiver(link->can_hub.get())); + } + ++id; +} + +void packet_complete(unsigned index) +{ + g_packet_data.erase(index); +} + +struct SendPacketRequest +{ + /// Packet number to output (sequence number). + unsigned index = 0; + /// When was this packet generated by the timer. + uint64_t generateTsNsec = 0; +}; + +/// Implements the state machine to acquire a buffer and send an outgoing +/// packet. +class SendFlow : public StateFlow, QList<1>> +{ +public: + SendFlow() + : StateFlow, QList<1>>(&g_service) + { } + + Action entry() override + { + auto it = g_packet_data.find(message()->data()->index); + if (it != g_packet_data.end()) + { + LOG(FATAL, + "duplicate packet index %d, %d, old send Ts %lld, new send ts " + "%" PRId64, + message()->data()->index, it->second.index_, + it->second.timerTs_, message()->data()->generateTsNsec); + DIE("duplicate packet"); + } + HASSERT(it == g_packet_data.end()); + pinfo_ = &g_packet_data[message()->data()->index]; + pinfo_->index_ = message()->data()->index; + pinfo_->flowTs_ = os_get_time_monotonic(); + pinfo_->timerTs_ = message()->data()->generateTsNsec; + return allocate_and_call( + output_port.can_hub.get(), STATE(have_buffer), &pool_); + } + + Action have_buffer() + { + g_pending_buffers++; + auto *b = get_allocation_result(output_port.can_hub.get()); + b->set_done(&pinfo_->bn_); + auto &f = *b->data()->mutable_frame(); + f.can_dlc = 8; + uint32_t idx_be = htobe32(pinfo_->index_); + switch(pkt_type) { + case PacketType::EVENT_REPORT: { + SET_CAN_FRAME_ID_EFF(f, SEND_HEADER_EVENT); + memcpy(f.data, SEND_PAYLOAD, 8); + memcpy(f.data + 4, &idx_be, 4); + break; + } + case PacketType::EVENT_IDENTIFY: { + SET_CAN_FRAME_ID_EFF(f, SEND_HEADER_IDENT); + memcpy(f.data, SEND_PAYLOAD, 8); + memcpy(f.data + 4, &idx_be, 4); + break; + } + case PacketType::NODE_ID_VERIFY: { + SET_CAN_FRAME_ID_EFF(f, SEND_HEADER_NODEID); + f.can_dlc = 6; + openlcb::node_id_to_data(destination_nodeid, f.data); + break; + } + } + b->data()->skipMember_ = nullptr; + pinfo_->pendingReceivers_ = std::max(0, g_num_live_links - 1); + g_num_packets_sent++; + pinfo_->sendTs_ = os_get_time_monotonic(); + output_port.can_hub->send(b); + return release_and_exit(); + } + +private: + LimitedPool pool_ { + sizeof(Buffer>), SEND_PARALLELISM}; + PacketInfo *pinfo_; +} g_send_flow; + +/// This timer triggers packets to be sent by sending messages to the +/// SendFlow. +class PacketGenTimer : public ::Timer +{ +public: + PacketGenTimer() + : Timer(g_executor.active_timers()) + { } + + long long timeout() override + { + auto *b = g_send_flow.alloc(); + HASSERT(b); + b->data()->index = g_next_packet++; + b->data()->generateTsNsec = this->schedule_time(); + g_send_flow.send(b); + return RESTART; + } +} pkt_gen_timer; + +/// This timer prints stats every second. It is responsible for generating +/// deltas from absolute counters. +class StatsTimer : public ::Timer +{ +public: + StatsTimer() + : Timer(g_executor.active_timers()) + { } + + long long timeout() override + { + print_stats(); + return RESTART; + } + + void print_stats() + { + string ret; + ret += "Send: "; + unsigned d = update_diff(g_next_packet, &next_packet); + ret += StringPrintf("|+%" PRIu32 " gen", d); + d = update_diff(g_num_packets_accepted, &num_packets_accepted); + ret += StringPrintf("|+%d send complete", d); + d = update_diff(g_num_packets_all_received, &num_packets_all_received); + ret += StringPrintf("|+%d recv complete", d); + int send_q = -2; + ::ioctl(connections[0]->fd(), TIOCOUTQ, &send_q); + // @todo add stats about send queues. + ret += StringPrintf( + "||sendq %d|pending buf %u| sent>acc %u| acc>recv %d|", send_q, + g_pending_buffers, g_num_packets_sent - g_num_packets_accepted, + g_num_packets_accepted - g_num_packets_all_received); + ret += "\n"; + for (const auto &lnk : input_ports) + { + ret += lnk.receiver->get_stats(); + } + // @todo print using hubdeviceselect instead of blocking output. + printf("%s\n", ret.c_str()); + } + +private: + /// Creates a delta from a counter. + /// @param global_value current value of the global counter. + /// @param local_value a local shadow variable for the same counter. It + /// will be updated with the global value every time the function gets + /// called. + /// @return diff (number of counts elapsed since last call). + template T update_diff(T global_value, T *local_value) + { + T diff = global_value - *local_value; + *local_value = global_value; + return diff; + } + + /// Index of the next packet to generate. + uint32_t next_packet = 1; + /// How many buffers have been sent and waiting for the notification on + /// them. + // unsigned pending_buffers = 0; + + /// How many packets we have sent to the output iface. + // unsigned num_packets_sent = 0; + /// How many sent packets got their (send) barrier notified. + unsigned num_packets_accepted = 0; + /// How many sent packets got their (receive) barrier notified and removed + /// from storage. + unsigned num_packets_all_received = 0; + +} stats_timer; + +/** Entry point to application. + * @param argc number of command line arguments + * @param argv array of command line arguments + * @return 0, should never return + */ +int appl_main(int argc, char *argv[]) +{ + arg0 = argv[0]; + parse_args(argc, argv); + add_link(&output_port, false); + for (auto &l : input_ports) + { + add_link(&l, true); + } + + // g_executor.start_thread("executor_thread", 0, 5000); + + if (pkt_per_sec > 0) + { + long long diff = SEC_TO_NSEC(1) / pkt_per_sec; + pkt_gen_timer.start(diff); + } + microsleep(100); + stats_timer.start(SEC_TO_NSEC(1)); + + while (1) + { + int num_links = 0; + for (const auto &p : connections) + { + if (p->ping()) + { + num_links++; + } + } + g_num_live_links = num_links; + sleep(1); + } + + return 0; +} diff --git a/applications/load_test/targets/linux.x86/main.cxx b/applications/load_test/targets/linux.x86/main.cxx index 1aeb447ed..6c06b8326 100644 --- a/applications/load_test/targets/linux.x86/main.cxx +++ b/applications/load_test/targets/linux.x86/main.cxx @@ -26,7 +26,7 @@ * * \file main.cxx * - * Main file for the io board application on the Tiva Launchpad board. + * Main file for the load generator application for the load-test suite. * * @author Balazs Racz * @date 5 Jun 2015 diff --git a/arduino/OpenMRNLite.cpp b/arduino/OpenMRNLite.cpp index d38f6b283..d1968a199 100644 --- a/arduino/OpenMRNLite.cpp +++ b/arduino/OpenMRNLite.cpp @@ -43,7 +43,7 @@ OpenMRN::OpenMRN(openlcb::NodeID node_id) init(node_id); } -#ifdef ESP32 +#ifdef ESP_PLATFORM extern "C" { #ifndef OPENMRN_EXCLUDE_REBOOT_IMPL @@ -62,6 +62,6 @@ ssize_t os_get_free_heap() #endif // OPENMRN_EXCLUDE_FREE_HEAP_IMPL } -#endif // ESP32 +#endif // ESP_PLATFORM } // namespace openmrn_arduino diff --git a/arduino/OpenMRNLite.h b/arduino/OpenMRNLite.h index 87bcdbf29..ee97336b1 100644 --- a/arduino/OpenMRNLite.h +++ b/arduino/OpenMRNLite.h @@ -48,9 +48,8 @@ #include "utils/logging.h" #include "utils/Uninitialized.hxx" -#if defined(ESP32) +#if defined(ESP_PLATFORM) -#include #include #include @@ -70,8 +69,6 @@ constexpr UBaseType_t OPENMRN_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 1; #include "freertos_drivers/esp32/Esp32Gpio.hxx" #include "freertos_drivers/esp32/Esp32SocInfo.hxx" -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) - // If we are using ESP-IDF v4.3 (or later) enable the Esp32Ledc API. #include "freertos_drivers/esp32/Esp32Ledc.hxx" @@ -92,15 +89,6 @@ constexpr UBaseType_t OPENMRN_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 1; #endif // NOT ESP32-H2 and NOT ESP32-C2 -#endif // IDF v4.3+ - -#if defined(CONFIG_IDF_TARGET_ESP32) -// Note: This code is deprecated in favor of the TWAI interface which exposes -// both select() and fnctl() interfaces. Support for this may be removed in the -// future. -#include "freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx" -#endif // ESP32 only - #include "freertos_drivers/esp32/Esp32HardwareSerialAdapter.hxx" #include "freertos_drivers/esp32/Esp32WiFiManager.hxx" diff --git a/arduino/libify.sh b/arduino/libify.sh index d24a7883d..f06da354e 100755 --- a/arduino/libify.sh +++ b/arduino/libify.sh @@ -182,7 +182,7 @@ else fi copy_file src arduino/OpenMRNLite.{h,cpp} arduino/CDIXMLGenerator.hxx \ - include/{can_frame.h,nmranet_config.h,openmrn_features.h} \ + include/{can_frame.h,nmranet_config.h,openmrn_features.h,i2c.h,i2c-dev.h} \ include/freertos/{bootloader_hal.h,can_ioctl.h,endian.h,freertos_includes.h,stropts.h} \ include/freertos_select/ifaddrs.h diff --git a/include/can_frame.h b/include/can_frame.h index 7ebef825b..4e0028e7f 100644 --- a/include/can_frame.h +++ b/include/can_frame.h @@ -65,7 +65,7 @@ #elif defined (__nuttx__) || defined (__FreeRTOS__) || defined (__MACH__) || \ defined (__WIN32__) || defined (__EMSCRIPTEN__) || defined (ESP_NONOS) || \ - defined (ARDUINO) || defined (ESP32) + defined (ARDUINO) || defined (ESP_PLATFORM) #include struct can_frame diff --git a/include/freertos/can_ioctl.h b/include/freertos/can_ioctl.h index 69f5d228e..8b79d1d38 100644 --- a/include/freertos/can_ioctl.h +++ b/include/freertos/can_ioctl.h @@ -37,7 +37,7 @@ #include #ifdef __FreeRTOS__ #include "freertos/stropts.h" -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) #include "stropts.h" #endif diff --git a/include/freertos/freertos_includes.h b/include/freertos/freertos_includes.h index 86d444ffc..7245c39d3 100644 --- a/include/freertos/freertos_includes.h +++ b/include/freertos/freertos_includes.h @@ -1,12 +1,64 @@ -#ifdef ESP32 +/** \copyright + * Copyright (c) 2019, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file freertos_includes.h + * This file simplifies the include path for FreeRTOS header files between + * platforms. + * + * @author Balazs Racz + * @date 2 March 2019 + */ + +#ifdef ESP_PLATFORM #include #include #include +#include "sdkconfig.h" #define NSEC_TO_TICK(ns) \ (((((long long)(ns)) / 1000 * configTICK_RATE_HZ) + 999999) / 1000000) +// IDF v5.0 has introduced a configuration option (disabled by default) which +// enables the usage of legacy FreeRTOS data types, if that configuration option +// is *NOT* selected *AND* IDF v5.0+ is in use we need to add compatibility +// defines in order to compile OpenMRN successfully. +#if !defined(CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY) + +// used in os/os.c and os/os.h +#define portTickType TickType_t +#define xTaskHandle TaskHandle_t +#define xQueueHandle QueueHandle_t +#define xSemaphoreHandle SemaphoreHandle_t + +// used in freertos_drivers/arduino/CpuLoad.hxx and os/os.c +#define pcTaskGetTaskName pcTaskGetName + +#endif // IDF v5.0+ and !CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY + #else #include diff --git a/include/freertos/stropts.h b/include/freertos/stropts.h index b0df5ab77..2ec587fd9 100644 --- a/include/freertos/stropts.h +++ b/include/freertos/stropts.h @@ -38,7 +38,7 @@ extern "C" { #endif -#if defined(ESP32) +#if defined(ESP_PLATFORM) #include #else /** Request and ioctl transaction @@ -47,7 +47,7 @@ extern "C" { * @param ... key data (as a pointer or unsigned long type) */ int ioctl(int fd, unsigned long int key, ...); -#endif // ESP32 +#endif // ESP_PLATFORM /** ioctl key value for operation (not read or write) */ #define IOC_NONE 0U diff --git a/include/i2c-dev.h b/include/i2c-dev.h index 6b20e9c1e..ea208b434 100644 --- a/include/i2c-dev.h +++ b/include/i2c-dev.h @@ -36,7 +36,7 @@ #if defined (__linux__) #include -#elif defined (__FreeRTOS__) +#elif defined (__FreeRTOS__) || defined(ESP_PLATFORM) #include /** magic number for this driver's ioctl calls */ #define I2C_MAGIC ('i') diff --git a/include/i2c.h b/include/i2c.h index 8bf8af1e0..bf6253108 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -36,7 +36,7 @@ #if defined (__linux__) #include -#elif defined (__FreeRTOS__) +#elif defined (__FreeRTOS__) || defined(ESP_PLATFORM) #include /** Used in @ref i2c_rdwr_ioctl_data to describe a transaction segment. */ struct i2c_msg diff --git a/include/openmrn_features.h b/include/openmrn_features.h index 810e34cfd..79c5e2ba3 100644 --- a/include/openmrn_features.h +++ b/include/openmrn_features.h @@ -37,12 +37,12 @@ #ifndef _INCLUDE_OPENMRN_FEATURES_ #define _INCLUDE_OPENMRN_FEATURES_ -#ifdef ESP32 +#ifdef ESP_PLATFORM #include #else #define ESP_IDF_VERSION 0 #define ESP_IDF_VERSION_VAL(a,b,c) 1 -#endif // ESP32 +#endif // ESP_PLATFORM #ifdef __FreeRTOS__ /// Compiles the FreeRTOS event group based ::select() implementation. @@ -54,43 +54,50 @@ #define OPENMRN_FEATURE_REENT 1 #endif -#if defined(__FreeRTOS__) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) +#if defined(__FreeRTOS__) || defined(ESP_PLATFORM) // Note: this is not using OPENMRN_FEATURE_DEVICE_SELECT due to other usages // of that macro which may conflict with the ESP32 version of this feature. /// Adds support for FD based CAN interfaces. #define OPENMRN_FEATURE_FD_CAN_DEVICE 1 #endif -#if defined(__linux__) || defined(__MACH__) || defined(__WINNT__) || defined(ESP32) || defined(OPENMRN_FEATURE_DEVTAB) +#if defined(__linux__) || defined(__MACH__) || defined(__WINNT__) || \ + defined(ESP_PLATFORM) || defined(OPENMRN_FEATURE_DEVTAB) /// Enables the code using ::open ::close ::read ::write for non-volatile /// storage, FileMemorySpace for the configuration space, and /// SNIP_DYNAMIC_FILE_NAME for node names. #define OPENMRN_HAVE_POSIX_FD 1 #endif +#if !defined(ESP_PLATFORM) +/// Enables the code using ::fstat to confirm if the file handle is a socket. +#define OPENMRN_HAVE_SOCKET_FSTAT 1 +#endif + /// @todo this should probably be a whitelist: __linux__ || __MACH__. -#if !defined(__FreeRTOS__) && !defined(__WINNT__) && !defined(ESP32) && \ +#if !defined(__FreeRTOS__) && !defined(__WINNT__) && !defined(ESP_PLATFORM) && \ !defined(ARDUINO) && !defined(ESP_NONOS) /// Uses ::pselect in the Executor for sleep and pkill for waking up. #define OPENMRN_HAVE_PSELECT 1 #endif -#if defined(__WINNT__) || defined(ESP32) || defined(ESP_NONOS) +#if defined(__WINNT__) || defined(ESP_PLATFORM) || defined(ESP_NONOS) /// Uses ::select in the executor to sleep (unsure how wakeup is handled) #define OPENMRN_HAVE_SELECT 1 #endif -#if defined(OPENMRN_HAVE_SELECT) || defined(OPENMRN_HAVE_PSELECT) || defined(OPENMRN_FEATURE_DEVICE_SELECT) +#if defined(OPENMRN_HAVE_SELECT) || defined(OPENMRN_HAVE_PSELECT) || \ + defined(OPENMRN_FEATURE_DEVICE_SELECT) #define OPENMRN_FEATURE_EXECUTOR_SELECT 1 #endif -#if (defined(ARDUINO) && !defined(ESP32)) || defined(ESP_NONOS) || \ +#if (defined(ARDUINO) && !defined(ESP_PLATFORM)) || defined(ESP_NONOS) || \ defined(__EMSCRIPTEN__) /// A loop() function is calling the executor in the single-threaded OS context. #define OPENMRN_FEATURE_SINGLE_THREADED 1 #endif -#if defined(__FreeRTOS__) || defined(ESP32) +#if defined(__FreeRTOS__) || defined(ESP_PLATFORM) /// Use os_mutex_... implementation based on FreeRTOS mutex and semaphores. #define OPENMRN_FEATURE_MUTEX_FREERTOS 1 @@ -111,7 +118,7 @@ #define OPENMRN_FEATURE_SEM_TIMEDWAIT 1 #endif -#if defined(__FreeRTOS__) || defined(ESP32) +#if defined(__FreeRTOS__) || defined(ESP_PLATFORM) /// Use FreeRTOS implementation for os_thread_create and keeping a list of live /// threads. #define OPENMRN_FEATURE_THREAD_FREERTOS 1 @@ -131,7 +138,7 @@ #endif #if defined(__linux__) || defined(__MACH__) || defined(__FreeRTOS__) || \ - defined(ESP32) + defined(ESP_PLATFORM) /// Compiles support for BSD sockets API. #define OPENMRN_FEATURE_BSD_SOCKETS 1 @@ -141,7 +148,7 @@ #define OPENMRN_HAVE_BSD_SOCKETS_RX_TIMEOUT 1 #endif -#if !defined(__FreeRTOS__) && !defined(ESP32) +#if !defined(__FreeRTOS__) && !defined(ESP_PLATFORM) /// Compiles support for calling getsockname when binding a socket to a port /// when listening for incoming connections. #define OPENMRN_HAVE_BSD_SOCKETS_GETSOCKNAME 1 @@ -157,7 +164,7 @@ #define OPENMRN_FEATURE_BSD_SOCKETS_IGNORE_SIGPIPE 1 #endif -#if defined(__linux__) || defined(__MACH__) || defined(ESP32) +#if defined(__linux__) || defined(__MACH__) || defined(ESP_PLATFORM) /// Compiles support for reporting EOF as an error for read/write. #define OPENMRN_FEATURE_BSD_SOCKETS_REPORT_EOF_ERROR 1 #endif diff --git a/src/dcc/DccDebug.cxx b/src/dcc/DccDebug.cxx index ceb55acbd..58d1d6e7c 100644 --- a/src/dcc/DccDebug.cxx +++ b/src/dcc/DccDebug.cxx @@ -237,8 +237,13 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) bool is_activate = cmd & 1; cmd >>= 1; accy_address |= ((~cmd) & 0b111) << 9; - options += StringPrintf(" Accy %u %s", accy_address, - is_activate ? "activate" : "deactivate"); + unsigned user_address = + Defs::accy_address_binary_to_user(accy_address >> 1); + const char *n_r = + accy_address & 1 ? "closed/normal/on" : "thrown/reverse/off"; + const char* a_d = is_activate ? "activate" : "deactivate"; + options += StringPrintf( + " Accy %u (user %u %s) %s", accy_address, user_address, n_r, a_d); } else if (is_unknown_packet || is_svc_packet) { diff --git a/src/dcc/DccDebug.cxxtest b/src/dcc/DccDebug.cxxtest index 146b268df..42aa00f42 100644 --- a/src/dcc/DccDebug.cxxtest +++ b/src/dcc/DccDebug.cxxtest @@ -201,37 +201,40 @@ TEST(DccDebug, Accy) { Packet pkt; pkt.add_dcc_basic_accessory(0, false); - EXPECT_EQ("[dcc] Accy 0 deactivate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 0 (user 2045 thrown/reverse/off) deactivate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(1, false); - EXPECT_EQ("[dcc] Accy 1 deactivate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 1 (user 2045 closed/normal/on) deactivate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(2, false); - EXPECT_EQ("[dcc] Accy 2 deactivate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 2 (user 2046 thrown/reverse/off) deactivate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(3, true); - EXPECT_EQ("[dcc] Accy 3 activate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 3 (user 2046 closed/normal/on) activate", packet_to_string(pkt)); + pkt.clear(); + pkt.add_dcc_basic_accessory(7, true); + EXPECT_EQ("[dcc] Accy 7 (user 2048 closed/normal/on) activate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(33, false); - EXPECT_EQ("[dcc] Accy 33 deactivate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 33 (user 13 closed/normal/on) deactivate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(1023, false); - EXPECT_EQ("[dcc] Accy 1023 deactivate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 1023 (user 508 closed/normal/on) deactivate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(1024, false); - EXPECT_EQ("[dcc] Accy 1024 deactivate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 1024 (user 509 thrown/reverse/off) deactivate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(2044, true); - EXPECT_EQ("[dcc] Accy 2044 activate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 2044 (user 1019 thrown/reverse/off) activate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(2043, true); - EXPECT_EQ("[dcc] Accy 2043 activate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 2043 (user 1018 closed/normal/on) activate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(2042, true); - EXPECT_EQ("[dcc] Accy 2042 activate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 2042 (user 1018 thrown/reverse/off) activate", packet_to_string(pkt)); pkt.clear(); pkt.add_dcc_basic_accessory(4095, true); - EXPECT_EQ("[dcc] Accy 4095 activate", packet_to_string(pkt)); + EXPECT_EQ("[dcc] Accy 4095 (user 2044 closed/normal/on) activate", packet_to_string(pkt)); pkt.clear(); } diff --git a/src/dcc/Defs.cxx b/src/dcc/Defs.cxx new file mode 100644 index 000000000..a57e1db1c --- /dev/null +++ b/src/dcc/Defs.cxx @@ -0,0 +1,97 @@ +/** \copyright + * Copyright (c) 2024, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file dcc/Defs.cxx + * + * Helper functions for DCC concepts. + * + * @author Balazs Racz + * @date 7 Jan 2024 + */ + +#include "dcc/Defs.hxx" + +namespace dcc +{ +namespace Defs +{ + +bool decode_address_partition(uint16_t addr14, uint16_t *addr, + uint8_t *partition, dcc::TrainAddressType *atype) +{ + TrainAddressType _atype = TrainAddressType::UNSUPPORTED; + uint8_t _partition = 0xff; + uint16_t _addr = 0xffffu; + + addr14 &= (1u << 14) - 1; + uint8_t hibyte = addr14 >> 8; + if (hibyte <= MAX_MOBILE_LONG) + { + _atype = TrainAddressType::DCC_LONG_ADDRESS; + _partition = 0; + _addr = addr14; + } + else if ((hibyte & MASK_ACC_EXT) == ADR_ACC_EXT) + { + _atype = TrainAddressType::DCC_ACCY_EXT; + _partition = ADR_ACC_EXT; + _addr = addr14 & ~(MASK_ACC_EXT << 8); + } + else if ((hibyte & MASK_ACC_BASIC) == ADR_ACC_BASIC) + { + _atype = TrainAddressType::DCC_ACCY_BASIC_OUTPUT; + _partition = ADR_ACC_BASIC; + _addr = addr14 & ~(MASK_ACC_BASIC << 8); + } + else if (hibyte == ADR_MOBILE_SHORT) + { + _atype = TrainAddressType::DCC_SHORT_ADDRESS; + _partition = hibyte; + _addr = addr14 & 0x7f; + } + else + { + return false; + } + + if (addr) + { + *addr = _addr; + } + if (partition) + { + *partition = _partition; + } + if (atype) + { + *atype = _atype; + } + return true; +} + +} // namespace dcc::Defs + +} // namespace dcc diff --git a/src/dcc/Defs.cxxtest b/src/dcc/Defs.cxxtest new file mode 100644 index 000000000..41999c856 --- /dev/null +++ b/src/dcc/Defs.cxxtest @@ -0,0 +1,116 @@ +#include "dcc/Defs.hxx" + +#include "utils/test_main.hxx" + +class Addr14DecodeTest : public ::testing::Test +{ +protected: + bool do_decode(uint16_t addr14) + { + return dcc::Defs::decode_address_partition( + addr14, &addr_, &partition_, &atype_); + } + + // Decoding results go in here. + dcc::TrainAddressType atype_ = (dcc::TrainAddressType)99; + uint8_t partition_ = 255; + uint16_t addr_ = UINT16_MAX; +}; + +TEST_F(Addr14DecodeTest, decode_dcc_long) +{ + EXPECT_TRUE(do_decode(9987)); + EXPECT_EQ(dcc::TrainAddressType::DCC_LONG_ADDRESS, atype_); + EXPECT_EQ(9987u, addr_); + EXPECT_EQ(0u, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_long_max) +{ + EXPECT_TRUE(do_decode(10239)); + EXPECT_EQ(dcc::TrainAddressType::DCC_LONG_ADDRESS, atype_); + EXPECT_EQ(10239u, addr_); + EXPECT_EQ(0u, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_long_zero) +{ + EXPECT_TRUE(do_decode(0)); + EXPECT_EQ(dcc::TrainAddressType::DCC_LONG_ADDRESS, atype_); + EXPECT_EQ(0u, addr_); + EXPECT_EQ(0u, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_short) +{ + EXPECT_TRUE(do_decode((0b111000 << 8) | 33)); + EXPECT_EQ(dcc::TrainAddressType::DCC_SHORT_ADDRESS, atype_); + EXPECT_EQ(33u, addr_); + EXPECT_EQ(0b111000, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_short_max) +{ + EXPECT_TRUE(do_decode((0b111000 << 8) | 127)); + EXPECT_EQ(dcc::TrainAddressType::DCC_SHORT_ADDRESS, atype_); + EXPECT_EQ(127u, addr_); + EXPECT_EQ(0b111000, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_short_min) +{ + EXPECT_TRUE(do_decode((0b111000 << 8) | 1)); + EXPECT_EQ(dcc::TrainAddressType::DCC_SHORT_ADDRESS, atype_); + EXPECT_EQ(1u, addr_); + EXPECT_EQ(0b111000, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_short_zero) +{ + EXPECT_TRUE(do_decode((0b111000 << 8) | 0)); + EXPECT_EQ(dcc::TrainAddressType::DCC_SHORT_ADDRESS, atype_); + // This is not technically valid as a short address, but this is the best + // guess after decoding. + EXPECT_EQ(0u, addr_); + EXPECT_EQ(0b111000, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_basic_accy) +{ + EXPECT_TRUE(do_decode((0b110000 << 8) | 1328)); + EXPECT_EQ(dcc::TrainAddressType::DCC_ACCY_BASIC_OUTPUT, atype_); + EXPECT_EQ(1328u, addr_); + EXPECT_EQ(0b110000, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_basic_accy_max) +{ + EXPECT_TRUE(do_decode((0b110000 << 8) | 2047)); + EXPECT_EQ(dcc::TrainAddressType::DCC_ACCY_BASIC_OUTPUT, atype_); + EXPECT_EQ(2047u, addr_); + EXPECT_EQ(0b110000, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_ext_accy) +{ + EXPECT_TRUE(do_decode((0b101000 << 8) | 1328)); + EXPECT_EQ(dcc::TrainAddressType::DCC_ACCY_EXT, atype_); + EXPECT_EQ(1328u, addr_); + EXPECT_EQ(0b101000, partition_); +} + +TEST_F(Addr14DecodeTest, decode_dcc_ext_accy_max) +{ + EXPECT_TRUE(do_decode((0b101000 << 8) | 2047)); + EXPECT_EQ(dcc::TrainAddressType::DCC_ACCY_EXT, atype_); + EXPECT_EQ(2047u, addr_); + EXPECT_EQ(0b101000, partition_); +} + +TEST_F(Addr14DecodeTest, fails) +{ + EXPECT_FALSE(do_decode((0b111000 << 8) | 2047)); + EXPECT_EQ(99, (int)atype_); + EXPECT_EQ(UINT16_MAX, addr_); + EXPECT_EQ(0xff, partition_); +} diff --git a/src/dcc/Defs.hxx b/src/dcc/Defs.hxx index 183989185..f05769dc3 100644 --- a/src/dcc/Defs.hxx +++ b/src/dcc/Defs.hxx @@ -50,6 +50,12 @@ enum class TrainAddressType : uint8_t DCC_LONG_ADDRESS, /// Marklin-motorola packets. Addresses 1..80 are supported. MM, + /// Per-output addressing of DCC basic accessories (user facing is 1 to + /// 2048, internal is 0 to 2047) + DCC_ACCY_BASIC_OUTPUT, + /// DCC 11-bit extended accessory decoder address (user facing is 1 to + /// 2048, internal is 0 to 2047). + DCC_ACCY_EXT, /// Unsupported address type (e.g. a protocol we don't have an /// implementation for). UNSUPPORTED = 255, @@ -108,8 +114,31 @@ enum DCC_SVC_PAGED_VERIFY = 0b01110000, DCC_SVC_PAGED_MASK = 0b11111000, + // Byte 1 of dcc accessory control packet DCC_BASIC_ACCESSORY_B1 = 0b10000000, + // Mask to check on DCC_BASIC_ACCESSORY_B1 + DCC_BASIC_ACCESSORY_MASK1 = 0b11000000, + // Byte 2 of dcc basic accessory control packet DCC_BASIC_ACCESSORY_B2 = 0b10000000, + // Mask for dcc basic accessory control packet + DCC_BASIC_ACCESSORY_MASK2 = 0b10000000, + // Byte 2 of DCC extended accessory control packet + DCC_EXT_ACCESSORY_B2 = 0b00000001, + // Mask for DCC_EXT_ACCESSORY_B2 + DCC_EXT_ACCESSORY_MASK2 = 0b10001001, + + // Basic accy activate command (in byte 2) + DCC_BASIC_ACCESSORY_B2_ACTIVATE = 0b00001000, + // Basic accy deactivate command (in byte 2) + DCC_BASIC_ACCESSORY_B2_DEACTIVATE = 0b00000000, + // Mask for activate/deactivate + DCC_BASIC_ACCESSORY_B2_ACTIVATE_MASK = DCC_BASIC_ACCESSORY_B2_ACTIVATE, + // Basic accy thrown / reverse / red / off command (in byte 2) + DCC_BASIC_ACCESSORY_B2_THROWN = 0b00000000, + // Basic accy closed / normal / green / on command (in byte 2) + DCC_BASIC_ACCESSORY_B2_CLOSED = 0b00000001, + // Mask for thrown / closed + DCC_BASIC_ACCESSORY_B2_DIR_MASK = DCC_BASIC_ACCESSORY_B2_CLOSED, // Extended packet: 128-step speed. DCC_EXT_SPEED = 0b00111111, @@ -178,6 +207,59 @@ enum ADR_INVALID = (ADR_MOBILE_SHORT << 8), }; +/// Decodes a 14-bit address (according to S-9.2.1.1) into an address type and +/// a raw address. +/// @param addr14 a 14-bit address according to S-9.2.1.1 +/// @param addr the decoded address value (8 to 14 bits) +/// @param partition the partition of the address type (the top byte, max 6 bits, e.g. ADR_MOBILE_SHORT) +/// @param atype address type enum +/// @return true if the decoding is successful, false if the value is not +/// representable. +bool decode_address_partition(uint16_t addr14, uint16_t* addr, uint8_t* partition, dcc::TrainAddressType* atype); + +/// Convers a DCC basic or extended accessory decoder address from user address +/// (1-2048) to binary address (0..2047). This takes into account the 2023 +/// draft of the DCC standard for address mapping. It uses per-output +/// addressing for basic accessory decoders. It uses 2044 for the address that +/// previously was the broadcast address for basic accessory decoders. +/// @param user user address for accy decoders. 1..2040 uses the legacy +/// addressing. 2041 .. 2048 uses the 2023 RCN-213 and DCC S-9.2.1 encoding. +/// @return binary address to be used in the DCC packet. The one's complement +/// of the highest address bits is not yet applied. +static inline unsigned accy_address_user_to_binary(unsigned user) +{ + if (user >= 2045) + { + return user - 2045; + } + else + { + return user + 3; + } +} + +/// Convers a DCC basic or extended accessory decoder address from a binary +/// address (0 - 2047) to user address (1 - 2048). This takes into account the +/// 2023 draft of the DCC standard for address mapping. It uses per-output +/// addressing for basic accessory decoders. It uses 2044 user address for the +/// binary address that previously was the broadcast for basic accessory +/// decoders. +/// @param binary the address in the DCC packet. The one's complement for the +/// highest bits should be undone already. +/// @return user user address for accy decoders. 1..2040 uses the legacy +/// addressing. 2041 .. 2048 uses the 2023 RCN-213 and DCC S-9.2.1 encoding. +static inline unsigned accy_address_binary_to_user(unsigned binary) +{ + if (binary < 4) + { + return binary + 2045; + } + else + { + return binary - 3; + } +} + /// Parameters for the Logon Enable command. enum class LogonEnableParam { diff --git a/src/dcc/Packet.cxx b/src/dcc/Packet.cxx index 4866449c6..757a448d5 100644 --- a/src/dcc/Packet.cxx +++ b/src/dcc/Packet.cxx @@ -110,6 +110,18 @@ void Packet::add_dcc_address(DccLongAddress address) feedback_key = address.value; } +void Packet::add_dcc_accy_address(bool is_basic, unsigned address) +{ + start_dcc_packet(); + + payload[dlc++] = DCC_BASIC_ACCESSORY_B1 | ((address >> 2) & 0b111111); + uint8_t b2 = is_basic ? DCC_BASIC_ACCESSORY_B2 : DCC_EXT_ACCESSORY_B2; + b2 |= ((~(address >> 8)) & 0b111) << 4; + b2 |= (address & 0b11) << 1; + payload[dlc++] = b2; + feedback_key = 0x10000 | address; +} + void Packet::add_dcc_speed14(bool is_fwd, bool light, unsigned speed) { /// TODO(balazs.racz) add unittests. @@ -307,19 +319,41 @@ void Packet::set_dcc_svc_paged_verify_reg(uint8_t reg, uint8_t value) add_dcc_checksum(); } -void Packet::add_dcc_basic_accessory(unsigned address, bool is_activate) { - payload[dlc++] = DCC_BASIC_ACCESSORY_B1 | ((address >> 3) & 0b111111); - uint8_t b2 = 1; - b2 <<= 3; - b2 |= ((~(address >> 9)) & 0b111); - b2 <<= 1; - b2 |= (is_activate ? 1 : 0); - b2 <<= 3; - b2 |= address & 0b111; - payload[dlc++] = b2; +void Packet::set_dcc_basic_accy_params(bool is_normal, bool is_activate) +{ + if (is_normal) + { + payload[1] |= DCC_BASIC_ACCESSORY_B2_CLOSED; + } + else + { + payload[1] |= DCC_BASIC_ACCESSORY_B2_THROWN; + } + if (is_activate) + { + payload[1] |= DCC_BASIC_ACCESSORY_B2_ACTIVATE; + } + else + { + payload[1] |= DCC_BASIC_ACCESSORY_B2_DEACTIVATE; + } +} + +void Packet::add_dcc_basic_accessory(unsigned address, bool is_activate) +{ + add_dcc_accy_address(true, address >> 1); + set_dcc_basic_accy_params(address & 1, is_activate); + add_dcc_checksum(); +} + +void Packet::add_dcc_ext_accessory(unsigned address, uint8_t aspect) +{ + add_dcc_accy_address(false, address); + payload[dlc++] = aspect; add_dcc_checksum(); } + void Packet::set_dcc_logon_enable( Defs::LogonEnableParam param, uint16_t cid, uint8_t session_id) { diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index b3bda0d7d..530c51968 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -335,6 +335,60 @@ TEST_F(PacketTest, DccBasicAccyOn) EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b1 ^ b2)); } +TEST(DefsTest, AccyAddress) +{ + for (unsigned user = 1; user <= 2048; ++user) + { + SCOPED_TRACE(user); + // Inversion should be OK. + EXPECT_EQ(user, + Defs::accy_address_binary_to_user( + Defs::accy_address_user_to_binary(user))); + } + for (unsigned binary = 0; binary <= 2047; ++binary) + { + SCOPED_TRACE(binary); + // Inversion should be OK. + EXPECT_EQ(binary, + Defs::accy_address_user_to_binary( + Defs::accy_address_binary_to_user(binary))); + } + // First address is 4. + EXPECT_EQ(4u, Defs::accy_address_user_to_binary(1)); + // This address gets translated to the former broadcast address. + EXPECT_EQ(2047u, Defs::accy_address_user_to_binary(2044)); + // Rollover at 2045 + EXPECT_EQ(0u, Defs::accy_address_user_to_binary(2045)); + // Highest address is 2048 + EXPECT_EQ(3u, Defs::accy_address_user_to_binary(2048)); +} + +TEST_F(PacketTest, DccBasicAccyPom) +{ + pkt_.add_dcc_accy_address(true, Defs::accy_address_user_to_binary(3)); + pkt_.set_dcc_basic_accy_params(false, true); + pkt_.add_dcc_pom_write1(11, 6); + EXPECT_THAT(get_packet(), + ElementsAre(0b10000001, 0b11111100, 0b11101100, 0b00001011, 0b00000110, + 0b10011100)); +} + +TEST_F(PacketTest, DccExtAccySet) +{ + pkt_.add_dcc_ext_accessory(1733, 0x5a); + // 1733 = 0x6c5 = 0b 110 1100 0101 + uint8_t b1 = 0b10110001; + uint8_t b2 = 0b00010011; + uint8_t b3 = 0x5a; + EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); + + pkt_.clear(); + pkt_.add_dcc_ext_accessory(1733, 0x80); + b3 = 0x80; + EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); +} + + TEST_F(PacketTest, SvcProgDirectByte) { pkt_.set_dcc_svc_verify_byte(651, 0x55); @@ -930,5 +984,4 @@ TEST_F(Train128Test, MaxSpeed) EXPECT_THAT(get_packet(), ElementsAre(55, 0b00111111, 0x7F, _)); } - } // namespace dcc diff --git a/src/dcc/Packet.hxx b/src/dcc/Packet.hxx index f26d9a134..712191d98 100644 --- a/src/dcc/Packet.hxx +++ b/src/dcc/Packet.hxx @@ -116,6 +116,13 @@ struct Packet : public DCCPacket /// Adds the header to the packet needed for addressing a DCC /// locomotive. @param address is the DCC (long) address. void add_dcc_address(DccLongAddress address); + /// Adds the header to the packet needed for addressing a DCC + /// accessory (e.g. for POM). + /// @param is_basic true for basic accessory, false for extended accessory + /// @param address the 11-bit accessory address, 0..2047. This is NOT the + /// user-visible address (that one is rotated by 3). For basic accessories + /// this is addressing an output number. + void add_dcc_accy_address(bool is_basic, unsigned address); /** Adds a speed-and-direction command (dcc baseline command) ot the * packet. Speed is maximum 14. This should be called after @@ -298,6 +305,23 @@ struct Packet : public DCCPacket */ void add_dcc_basic_accessory(unsigned address, bool is_activate); + /// Call this function after setting a basic accy address to set the + /// accessory packet options. + /// @param is_normal true for normal, false for reverse + /// @param is_activate true for activate, false for deactivate + void set_dcc_basic_accy_params(bool is_normal, bool is_activate); + + /// Adds a DCC extended accessory decoder command packet and the checksum + /// byte. + /// @param address is the 11-bit binary address, 0..2047. No bits have to be + /// inverted. This will be A10..A0 on the track. (To convert from a user + /// address, see accy_address_user_to_binary in dcc::Defs.) + /// @param aspect is the argument byte to the extended + /// accessory. Traditionally this was used as an aspect for a signal + /// decoder, but different accessories might have different interpretation + /// of it. + void add_dcc_ext_accessory(unsigned address, uint8_t aspect); + /// Sets the packet to a logon enable packet. /// @param param defines which decoders should be requested to logon. /// @param cid the command station unique ID hashed. diff --git a/src/executor/Executor.cxx b/src/executor/Executor.cxx index 310e7ce18..3c05ed52e 100644 --- a/src/executor/Executor.cxx +++ b/src/executor/Executor.cxx @@ -386,7 +386,13 @@ void ExecutorBase::wait_with_select(long long wait_length) fd_set fd_r(selectRead_); fd_set fd_w(selectWrite_); fd_set fd_x(selectExcept_); - if (!empty()) { + // We will check the queue for any prior wakeups after this call. If we + // already processed the executables, the wakeup is not necessary. Without + // this clear, there would always be two select() iterations happening when + // we are done with work and can go to sleep. + selectHelper_.clear_wakeup(); + if (!empty()) + { wait_length = 0; } long long max_sleep = MSEC_TO_NSEC(config_executor_max_sleep_msec()); diff --git a/src/executor/Executor.hxx b/src/executor/Executor.hxx index 39cd8c57f..f00c6dfb9 100644 --- a/src/executor/Executor.hxx +++ b/src/executor/Executor.hxx @@ -165,7 +165,7 @@ public: /// Helper function for debugging and tracing. /// @return currently running executable or nullptr if none active. - Executable* volatile current() { return current_; } + Executable* current() { return current_; } protected: /** Thread entry point. @@ -323,7 +323,7 @@ public: */ void add_from_isr(Executable *msg, unsigned priority = UINT_MAX) override { -#ifdef ESP32 +#ifdef ESP_PLATFORM // On the ESP32 we need to call insert instead of insert_locked to // ensure that all code paths lock the queue for consistency since // this code path is not guaranteed to be protected by a critical @@ -333,7 +333,7 @@ public: #else queue_.insert_locked( msg, priority >= NUM_PRIO ? NUM_PRIO - 1 : priority); -#endif // ESP32 +#endif // ESP_PLATFORM selectHelper_.wakeup_from_isr(); } #endif // OPENMRN_FEATURE_RTOS_FROM_ISR diff --git a/src/executor/StateFlow.hxx b/src/executor/StateFlow.hxx index d52b83817..7cffff065 100644 --- a/src/executor/StateFlow.hxx +++ b/src/executor/StateFlow.hxx @@ -355,6 +355,7 @@ protected: Pool *p = pool; if (!p) { + HASSERT(target_flow != nullptr); p = target_flow->pool(); } LOG(VERBOSE, "allocate from pool %p, main pool %p", p, mainBufferPool); @@ -736,14 +737,12 @@ protected: */ Action listen_and_call(StateFlowSelectHelper *helper, int fd, Callback c) { -// ESP-IDF does not implement fstat for the LwIP VFS layer -// https://github.com/espressif/esp-idf/issues/7198 -#ifndef ESP32 +#if OPENMRN_HAVE_SOCKET_FSTAT // verify that the fd is a socket struct stat stat; fstat(fd, &stat); HASSERT(S_ISSOCK(stat.st_mode)); -#endif // ESP32 +#endif // OPENMRN_HAVE_SOCKET_FSTAT helper->reset(Selectable::READ, fd, Selectable::MAX_PRIO); helper->set_wakeup(this); @@ -759,14 +758,12 @@ protected: */ Action connect_and_call(StateFlowSelectHelper *helper, int fd, Callback c) { -// ESP-IDF does not implement fstat for the LwIP VFS layer -// https://github.com/espressif/esp-idf/issues/7198 -#ifndef ESP32 +#if OPENMRN_HAVE_SOCKET_FSTAT // verify that the fd is a socket struct stat stat; fstat(fd, &stat); HASSERT(S_ISSOCK(stat.st_mode)); -#endif // ESP32 +#endif // OPENMRN_HAVE_SOCKET_FSTAT helper->reset(Selectable::WRITE, fd, Selectable::MAX_PRIO); helper->set_wakeup(this); diff --git a/src/executor/Timer.hxx b/src/executor/Timer.hxx index 61047d2b1..04ab1eeb1 100644 --- a/src/executor/Timer.hxx +++ b/src/executor/Timer.hxx @@ -168,6 +168,15 @@ public: * @returns the new timer period, or one of the above special values. */ virtual long long timeout() = 0; + /// @return the time when this timer is triggered. This may be in the past, + /// if the timer has just woken up and we are in the timeout() function. It + /// may be in the future if this timer is scheduled. It may be zero if this + /// timer was never scheduled. + long long schedule_time() + { + return when_; + } + /** Starts a timer. The timer must not be active, and neither expired at * the time of call. * @param period period in nanoseconds before expiration. If not specified, diff --git a/src/freertos_drivers/common/libatomic.c b/src/freertos_drivers/common/libatomic.c index bd7db1f98..094286e52 100644 --- a/src/freertos_drivers/common/libatomic.c +++ b/src/freertos_drivers/common/libatomic.c @@ -35,7 +35,7 @@ #include -#if defined(STM32F0xx) || (!defined(ARDUINO) && !defined(ESP32)) +#if defined(STM32F0xx) || (!defined(ARDUINO) && !defined(ESP_PLATFORM)) // On Cortex-M0 the only way to do atomic operation is to disable interrupts. /// Disables interrupts and saves the interrupt enable flag in a register. diff --git a/src/freertos_drivers/esp32/Esp32AdcOneShot.hxx b/src/freertos_drivers/esp32/Esp32AdcOneShot.hxx new file mode 100644 index 000000000..8af0ee0a3 --- /dev/null +++ b/src/freertos_drivers/esp32/Esp32AdcOneShot.hxx @@ -0,0 +1,217 @@ +/** \copyright + * Copyright (c) 2023, Mike Dunston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Esp32AdcOneShot.hxx + * + * Helper declarations for using ADC pins via the ADC One-Shot ESP-IDF APIs. + * + * @author Mike Dunston + * @date 8 Feburary 2023 + */ + +#ifndef _DRIVERS_ESP32ADCONESHOT_HXX_ +#define _DRIVERS_ESP32ADCONESHOT_HXX_ + +#include "utils/logging.h" + +#if defined(__has_include) +#if __has_include() +// include legacy types so ADC1_CHANNEL_0 and ADC1_CHANNEL_0_GPIO_NUM etc are +// defined, soc/adc_channel.h includes references to these on release/v5.0 +// branch and is planned for update in later relase versions. +#include +#endif // __has_include driver/adc_types_legacy.h +#endif // has_include + +#include +#include + +/// Defines an ADC input pin. +/// +/// Do not use this class directly. Use @ref ADC_PIN instead. +template struct Esp32ADCInput : public Defs +{ +public: + using Defs::ATTEN; + using Defs::BITS; + using Defs::CHANNEL; + using Defs::PIN; + using Defs::HANDLE; + + static void hw_init() + { + // due to using #if/#elif/#endif it is not possible to include this in + // the ADC_PIN wrapper code. + const adc_oneshot_unit_init_cfg_t unit_config = + { +#if CONFIG_IDF_TARGET_ESP32 + .unit_id = PIN >= 30 ? ADC_UNIT_1 : ADC_UNIT_2, +#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + .unit_id = PIN <= 10 ? ADC_UNIT_1 : ADC_UNIT_2, +#elif CONFIG_IDF_TARGET_ESP32C3 + .unit_id = PIN <= 4 ? ADC_UNIT_1 : ADC_UNIT_2, +#endif + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + const adc_oneshot_chan_cfg_t channel_config = + { + .atten = ATTEN, + .bitwidth = BITS, + }; + + LOG(VERBOSE, + "[Esp32ADCInput] Configuring ADC%d:%d input pin %d, " + "attenuation %d, bits %d", + unit_config.unit_id, CHANNEL, PIN, ATTEN, BITS); + ESP_ERROR_CHECK(adc_oneshot_new_unit(&unit_config, &HANDLE)); + ESP_ERROR_CHECK( + adc_oneshot_config_channel(HANDLE, CHANNEL, &channel_config)); + } + + /// NO-OP + static void hw_set_to_safe() + { + // NO-OP + } + + /// NO-OP + static void set(bool value) + { + // NO-OP + } + + static int sample() + { + int value = 0; + ESP_ERROR_CHECK(adc_oneshot_read(HANDLE, CHANNEL, &value)); + return value; + } +}; + +/// Helper macro for an ADC GPIO input on the ESP32. +/// +/// @param NAME is the basename of the declaration. For NAME==FOO the macro +/// declared FOO_Pin as a structure on which the read-write functions will be +/// available. +/// @param ADC_CHANNEL is the ADC channel to configure. +/// @param ATTENUATION is the voltage range for the ADC input. +/// @param BIT_RANGE is the bit range to configure the ADC to use. +/// +/// Supported ATTENUATION values and voltage ranges: +/// ADC_ATTEN_DB_0 - 0dB attenuaton gives full-scale voltage 1.1V +/// ADC_ATTEN_DB_2_5 - 2.5dB attenuation gives full-scale voltage 1.5V +/// ADC_ATTEN_DB_6 - 6dB attenuation gives full-scale voltage 2.2V +/// ADC_ATTEN_DB_11 - 11dB attenuation gives full-scale voltage 3.9V +/// +/// Supported BIT_RANGE values and ADC sample values: +/// ADC_WIDTH_BIT_9 - 0-511 +/// ADC_WIDTH_BIT_10 - 0-1023 +/// ADC_WIDTH_BIT_11 - 0-2047 +/// ADC_WIDTH_BIT_12 - 0-4065 +/// ADC_WIDTH_BIT_13 - 0-8191 -- Only valid on the ESP32-S2 and ESP32-S3. +/// NOTE: When using ADC1_CHANNEL_X this bit range will be applied to all +/// ADC1 channels, it is not recommended to mix values for ADC1 channels. +/// +/// Supported ADC_CHANNEL values and pin assignments for the ESP32: +/// ADC1_CHANNEL_0 : 36 +/// ADC1_CHANNEL_1 : 37 -- NOTE: Not recommended for use, see note below. +/// ADC1_CHANNEL_2 : 38 -- NOTE: Not recommended for use, see note below. +/// ADC1_CHANNEL_3 : 39 +/// ADC1_CHANNEL_4 : 32 +/// ADC1_CHANNEL_5 : 33 +/// ADC1_CHANNEL_6 : 34 +/// ADC1_CHANNEL_7 : 35 +/// ADC2_CHANNEL_0 : 4 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_1 : 0 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_2 : 2 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_3 : 15 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_4 : 13 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_5 : 12 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_6 : 14 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_7 : 27 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_8 : 25 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_9 : 29 -- NOTE: Not usable when WiFi is active. +/// NOTE: ADC1_CHANNEL_1 and ADC1_CHANNEL_2 typically have a capacitor which +/// connects to ADC1_CHANNEL_0 or ADC1_CHANNEL_3. The only known exception to +/// this is for some ESP32-PICO-D4/ESP32-PICO-V3 based boards, confirm on the +/// board schematic before using these pins. +/// +/// Supported ADC_CHANNEL values and pin assignments for the ESP32-S2/ESP32-S3: +/// ADC1_CHANNEL_0 : 1 +/// ADC1_CHANNEL_1 : 2 +/// ADC1_CHANNEL_2 : 3 +/// ADC1_CHANNEL_3 : 4 +/// ADC1_CHANNEL_4 : 5 +/// ADC1_CHANNEL_5 : 6 +/// ADC1_CHANNEL_6 : 7 +/// ADC1_CHANNEL_7 : 8 +/// ADC1_CHANNEL_8 : 9 +/// ADC1_CHANNEL_9 : 10 +/// ADC2_CHANNEL_0 : 11 +/// ADC2_CHANNEL_1 : 12 +/// ADC2_CHANNEL_2 : 13 +/// ADC2_CHANNEL_3 : 14 +/// ADC2_CHANNEL_4 : 15 +/// ADC2_CHANNEL_5 : 16 +/// ADC2_CHANNEL_6 : 17 +/// ADC2_CHANNEL_7 : 18 +/// ADC2_CHANNEL_8 : 19 -- NOTE: This pin is also used for USB PHY (D-). +/// ADC2_CHANNEL_9 : 20 -- NOTE: This pin is also used for USB PHY (D+). +/// +/// Supported ADC_CHANNEL values and pin assignments for the ESP32-C3: +/// ADC1_CHANNEL_0 : 0 +/// ADC1_CHANNEL_1 : 1 +/// ADC1_CHANNEL_2 : 2 +/// ADC1_CHANNEL_3 : 3 +/// ADC1_CHANNEL_4 : 4 +/// ADC2_CHANNEL_0 : 5 +/// +/// Example: +/// ADC_PIN(SENSE, ADC1_CHANNEL_0, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12); +/// ... +/// int level = SENSE_Pin::sample(); +#define ADC_PIN(NAME, ADC_CHANNEL, ATTENUATION, BIT_RANGE) \ + struct NAME##Defs \ + { \ + static const adc_channel_t CHANNEL = (adc_channel_t)ADC_CHANNEL; \ + static const gpio_num_t PIN = (gpio_num_t)ADC_CHANNEL##_GPIO_NUM; \ + static const adc_atten_t ATTEN = (adc_atten_t)ATTENUATION; \ + static const adc_bitwidth_t BITS = (adc_bitwidth_t)BIT_RANGE; \ + static adc_oneshot_unit_handle_t HANDLE; \ + public: \ + static const gpio_num_t pin() \ + { \ + return PIN; \ + } \ + static const adc_channel_t channel() \ + { \ + return CHANNEL; \ + } \ + }; \ + adc_oneshot_unit_handle_t NAME##Defs::HANDLE; \ + typedef Esp32ADCInput NAME##_Pin + +#endif // _DRIVERS_ESP32ADCONESHOT_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx b/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx index a1303f06b..8fb8b3b8b 100644 --- a/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx +++ b/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx @@ -40,11 +40,6 @@ #define _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_ #include "sdkconfig.h" -#include - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) -#error ESP32 Bootloader is only supported with ESP-IDF v4.3+ -#endif // IDF v4.3+ #ifndef BOOTLOADER_LOG_LEVEL #define BOOTLOADER_LOG_LEVEL VERBOSE @@ -56,13 +51,13 @@ // Enable streaming support for the bootloader #define BOOTLOADER_STREAM - #ifndef WRITE_BUFFER_SIZE -// Set the buffer size to half of the sector size to minimize the flash writes +// Set the buffer size to half of the sector size to minimize the flash writes. #define WRITE_BUFFER_SIZE (CONFIG_WL_SECTOR_SIZE / 2) #endif // WRITE_BUFFER_SIZE #include + #include #include #include @@ -583,4 +578,4 @@ void esp32_bootloader_run(uint64_t id, gpio_num_t rx, gpio_num_t tx, } } -#endif // _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_ \ No newline at end of file +#endif // _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32Gpio.hxx b/src/freertos_drivers/esp32/Esp32Gpio.hxx index 3a58f8fcc..b9f6fbb71 100644 --- a/src/freertos_drivers/esp32/Esp32Gpio.hxx +++ b/src/freertos_drivers/esp32/Esp32Gpio.hxx @@ -36,27 +36,15 @@ #define _DRIVERS_ESP32GPIO_HXX_ #include "freertos_drivers/arduino/GpioWrapper.hxx" +#include "freertos_drivers/esp32/Esp32AdcOneShot.hxx" #include "os/Gpio.hxx" #include "utils/logging.h" #include "utils/macros.h" -#include #include -#include -#include - -// esp_rom_gpio.h is a target agnostic replacement for esp32/rom/gpio.h -#if __has_include() #include -#elif ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) -#include -#else -#include -#endif -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) #include -#endif #if defined(CONFIG_IDF_TARGET_ESP32C3) /// Helper macro to test if a pin has been configured for output. @@ -94,7 +82,7 @@ public: static_assert(!(PIN_NUM >= 26 && PIN_NUM <= 32) , "Pin is reserved for flash usage."); #if defined(CONFIG_SPIRAM_MODE_OCT) || defined(CONFIG_ESPTOOLPY_OCT_FLASH) - static_assert(!(PIN_NUM >= 33 && PIN_NUM <= 37)), + static_assert(!(PIN_NUM >= 33 && PIN_NUM <= 37), "Pin is not available when Octal SPI mode is enabled."); #endif // ESP32S3 with Octal SPI #elif CONFIG_IDF_TARGET_ESP32S2 @@ -126,7 +114,7 @@ public: #else static_assert(!(PIN_NUM >= 6 && PIN_NUM <= 11) , "Pin is reserved for flash usage."); -#if defined(BOARD_HAS_PSRAM) +#if defined(BOARD_HAS_PSRAM) || defined(CONFIG_SPIRAM_SUPPORT) static_assert(PIN_NUM != 16 && PIN_NUM != 17 , "Pin is reserved for PSRAM usage."); #endif // BOARD_HAS_PSRAM @@ -238,11 +226,7 @@ public: LOG(VERBOSE, "[Esp32Gpio] Configuring output pin %d, default value: %d", PIN_NUM, SAFE_VALUE); -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) - gpio_pad_select_gpio(PIN_NUM); -#else // IDF v4.4 (or later) esp_rom_gpio_pad_select_gpio(PIN_NUM); -#endif // IDF v4.3 (or earlier) gpio_config_t cfg; memset(&cfg, 0, sizeof(gpio_config_t)); cfg.pin_bit_mask = BIT64(PIN_NUM); @@ -358,11 +342,7 @@ public: { LOG(VERBOSE, "[Esp32Gpio] Configuring input pin %d, PUEN: %d, PDEN: %d", PIN_NUM, PUEN, PDEN); -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) - gpio_pad_select_gpio(PIN_NUM); -#else // IDF v4.4 (or later) esp_rom_gpio_pad_select_gpio(PIN_NUM); -#endif // IDF v4.3 (or earlier) gpio_config_t cfg; memset(&cfg, 0, sizeof(gpio_config_t)); cfg.pin_bit_mask = BIT64(PIN_NUM); @@ -420,71 +400,6 @@ template struct GpioInputPUPD : public GpioInputPin struct Esp32ADCInput : public Defs -{ -public: - using Defs::CHANNEL; - using Defs::PIN; - using Defs::ATTEN; - using Defs::BITS; -#if CONFIG_IDF_TARGET_ESP32 - static const adc_unit_t UNIT = PIN >= 30 ? ADC_UNIT_1 : ADC_UNIT_2; -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - static const adc_unit_t UNIT = PIN <= 10 ? ADC_UNIT_1 : ADC_UNIT_2; -#elif CONFIG_IDF_TARGET_ESP32C3 - static const adc_unit_t UNIT = PIN <= 4 ? ADC_UNIT_1 : ADC_UNIT_2; -#endif - static void hw_init() - { - LOG(VERBOSE, - "[Esp32ADCInput] Configuring ADC%d:%d input pin %d, " - "attenuation %d, bits %d", - UNIT + 1, CHANNEL, PIN, ATTEN, BITS); - - if (UNIT == ADC_UNIT_1) - { - ESP_ERROR_CHECK(adc1_config_width(BITS)); - ESP_ERROR_CHECK( - adc1_config_channel_atten((adc1_channel_t)CHANNEL, ATTEN)); - } - else - { - ESP_ERROR_CHECK( - adc2_config_channel_atten((adc2_channel_t)CHANNEL, ATTEN)); - } - } - - /// NO-OP - static void hw_set_to_safe() - { - // NO-OP - } - - /// NO-OP - static void set(bool value) - { - // NO-OP - } - - static int sample() - { - int value = 0; - if (UNIT == ADC_UNIT_1) - { - value = adc1_get_raw((adc1_channel_t)CHANNEL); - } - else - { - ESP_ERROR_CHECK( - adc2_get_raw((adc2_channel_t)CHANNEL, BITS, &value)); - } - return value; - } -}; - /// Helper macro for defining GPIO pins on the ESP32. /// /// @param NAME is the basename of the declaration. For NAME==FOO the macro @@ -637,113 +552,13 @@ public: struct NAME##Defs \ { \ static const gpio_num_t PIN_NUM = (gpio_num_t)NUM; \ + \ public: \ - static const gpio_num_t pin() \ + static gpio_num_t pin() \ { \ return PIN_NUM; \ } \ }; \ typedef BaseClass NAME##_Pin -/// Helper macro for an ADC GPIO input on the ESP32. -/// -/// @param NAME is the basename of the declaration. For NAME==FOO the macro -/// declared FOO_Pin as a structure on which the read-write functions will be -/// available. -/// @param ADC_CHANNEL is the ADC channel to configure. -/// @param ATTENUATION is the voltage range for the ADC input. -/// @param BIT_RANGE is the bit range to configure the ADC to use. -/// -/// Supported ATTENUATION values and voltage ranges: -/// ADC_ATTEN_DB_0 - 0dB attenuaton gives full-scale voltage 1.1V -/// ADC_ATTEN_DB_2_5 - 2.5dB attenuation gives full-scale voltage 1.5V -/// ADC_ATTEN_DB_6 - 6dB attenuation gives full-scale voltage 2.2V -/// ADC_ATTEN_DB_11 - 11dB attenuation gives full-scale voltage 3.9V -/// -/// Supported BIT_RANGE values and ADC sample values: -/// ADC_WIDTH_BIT_9 - 0-511 -/// ADC_WIDTH_BIT_10 - 0-1023 -/// ADC_WIDTH_BIT_11 - 0-2047 -/// ADC_WIDTH_BIT_12 - 0-4065 -/// ADC_WIDTH_BIT_13 - 0-8191 -- Only valid on the ESP32-S2 and ESP32-S3. -/// NOTE: When using ADC1_CHANNEL_X this bit range will be applied to all -/// ADC1 channels, it is not recommended to mix values for ADC1 channels. -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32: -/// ADC1_CHANNEL_0 : 36 -/// ADC1_CHANNEL_1 : 37 -- NOTE: Not recommended for use, see note below. -/// ADC1_CHANNEL_2 : 38 -- NOTE: Not recommended for use, see note below. -/// ADC1_CHANNEL_3 : 39 -/// ADC1_CHANNEL_4 : 32 -/// ADC1_CHANNEL_5 : 33 -/// ADC1_CHANNEL_6 : 34 -/// ADC1_CHANNEL_7 : 35 -/// ADC2_CHANNEL_0 : 4 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_1 : 0 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_2 : 2 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_3 : 15 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_4 : 13 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_5 : 12 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_6 : 14 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_7 : 27 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_8 : 25 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_9 : 29 -- NOTE: Not usable when WiFi is active. -/// NOTE: ADC1_CHANNEL_1 and ADC1_CHANNEL_2 typically have a capacitor which -/// connects to ADC1_CHANNEL_0 or ADC1_CHANNEL_3. The only known exception to -/// this is for some ESP32-PICO-D4/ESP32-PICO-V3 based boards, confirm on the -/// board schematic before using these pins. -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32-S2/ESP32-S3: -/// ADC1_CHANNEL_0 : 1 -/// ADC1_CHANNEL_1 : 2 -/// ADC1_CHANNEL_2 : 3 -/// ADC1_CHANNEL_3 : 4 -/// ADC1_CHANNEL_4 : 5 -/// ADC1_CHANNEL_5 : 6 -/// ADC1_CHANNEL_6 : 7 -/// ADC1_CHANNEL_7 : 8 -/// ADC1_CHANNEL_8 : 9 -/// ADC1_CHANNEL_9 : 10 -/// ADC2_CHANNEL_0 : 11 -/// ADC2_CHANNEL_1 : 12 -/// ADC2_CHANNEL_2 : 13 -/// ADC2_CHANNEL_3 : 14 -/// ADC2_CHANNEL_4 : 15 -/// ADC2_CHANNEL_5 : 16 -/// ADC2_CHANNEL_6 : 17 -/// ADC2_CHANNEL_7 : 18 -/// ADC2_CHANNEL_8 : 19 -- NOTE: This pin is also used for USB PHY (D-). -/// ADC2_CHANNEL_9 : 20 -- NOTE: This pin is also used for USB PHY (D+). -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32-C3: -/// ADC1_CHANNEL_0 : 0 -/// ADC1_CHANNEL_1 : 1 -/// ADC1_CHANNEL_2 : 2 -/// ADC1_CHANNEL_3 : 3 -/// ADC1_CHANNEL_4 : 4 -/// ADC2_CHANNEL_0 : 5 -/// -/// Example: -/// ADC_PIN(SENSE, ADC1_CHANNEL_0, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12); -/// ... -/// int level = SENSE_Pin::sample(); -#define ADC_PIN(NAME, ADC_CHANNEL, ATTENUATION, BIT_RANGE) \ - struct NAME##Defs \ - { \ - static const adc_channel_t CHANNEL = (adc_channel_t)ADC_CHANNEL; \ - static const gpio_num_t PIN = (gpio_num_t)ADC_CHANNEL##_GPIO_NUM; \ - static const adc_atten_t ATTEN = (adc_atten_t)ATTENUATION; \ - static const adc_bits_width_t BITS = (adc_bits_width_t)BIT_RANGE; \ - public: \ - static const gpio_num_t pin() \ - { \ - return PIN; \ - } \ - static const adc_channel_t channel() \ - { \ - return CHANNEL; \ - } \ - }; \ - typedef Esp32ADCInput NAME##_Pin - -#endif // _DRIVERS_ESP32GPIO_HXX_ \ No newline at end of file +#endif // _DRIVERS_ESP32GPIO_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx b/src/freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx deleted file mode 100644 index 3a2123289..000000000 --- a/src/freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx +++ /dev/null @@ -1,428 +0,0 @@ -/** \copyright - * Copyright (c) 2019, Mike Dunston - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * \file Esp32HardwareCanAdapter.hxx - * - * ESP32 Hardware CAN adapter code. This utilizes the built in CAN controller - * to translate the can_frame in OpenMRN to the ESP32 can_message_t used by the - * ESP-IDF CAN controller code. The ESP32 will still require an external CAN - * transceiver (MCP2551 or SN65HVD230 as example). - * - * @author Mike Dunston - * @date 19 January 2019 - */ - -#ifndef _FREERTOS_DRIVERS_ESP32_ESP32HWCAN_HXX_ -#define _FREERTOS_DRIVERS_ESP32_ESP32HWCAN_HXX_ - -namespace openmrn_arduino -{ - -#include "freertos_drivers/arduino/Can.hxx" -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,2,0) -#include -#else // NOT IDF v4.2+ -#include - -// The following types and APIs are created as aliases as a compatibility for -// IDF v4.3+ which breaks due to driver/twai.h on the ESP32 attempting to -// override a few types created as part of can_ioctl.h -typedef can_timing_config_t twai_timing_config_t; -typedef can_filter_config_t twai_filter_config_t; -typedef can_general_config_t twai_general_config_t; -typedef can_status_info_t twai_status_info_t; -typedef can_message_t twai_message_t; -#define TWAI_TIMING_CONFIG_125KBITS CAN_TIMING_CONFIG_125KBITS -#define TWAI_FILTER_CONFIG_ACCEPT_ALL CAN_FILTER_CONFIG_ACCEPT_ALL -#define TWAI_MODE_NORMAL CAN_MODE_NORMAL -#define TWAI_IO_UNUSED CAN_IO_UNUSED -#define TWAI_ALERT_NONE CAN_ALERT_NONE -#define TWAI_STATE_BUS_OFF CAN_STATE_BUS_OFF -#define TWAI_STATE_RECOVERING CAN_STATE_RECOVERING -#define TWAI_MSG_FLAG_NONE CAN_MSG_FLAG_NONE -#define TWAI_MSG_FLAG_EXTD CAN_MSG_FLAG_EXTD -#define TWAI_MSG_FLAG_RTR CAN_MSG_FLAG_RTR -#define TWAI_MSG_FLAG_DLC_NON_COMP CAN_MSG_FLAG_DLC_NON_COMP - -#define twai_driver_install can_driver_install -#define twai_start can_start -#define twai_stop can_stop -#define twai_get_status_info can_get_status_info -#define twai_initiate_recovery can_initiate_recovery -#define twai_transmit can_transmit -#define twai_receive can_receive - -#endif // IDF v4.2+ - -#include -#include - -/// ESP32 CAN bus status strings, used for periodic status reporting -static const char *ESP32_CAN_STATUS_STRINGS[] = -{ - "STOPPED", // CAN_STATE_STOPPED - "RUNNING", // CAN_STATE_RUNNING - "OFF / RECOVERY NEEDED", // CAN_STATE_BUS_OFF - "RECOVERY UNDERWAY" // CAN_STATE_RECOVERING -}; - -class Esp32HardwareCanDeprecated : public Can -{ -public: - /// Constructor. - /// - /// @param name is the name for the CAN adapter, this is currently not used. - /// @param rxPin is the ESP32 pin that is connected to the external - /// transceiver RX. - /// @param txPin is the ESP32 pin that is connected to the external - /// transceiver TX. - Esp32HardwareCanDeprecated(const char *name, gpio_num_t rxPin, - gpio_num_t txPin, bool reportStats = true) - : Can(name) - , reportStats_(reportStats) - , overrunWarningPrinted_(false) - { - // Configure the ESP32 CAN driver to use 125kbps. - twai_timing_config_t can_timing_config = TWAI_TIMING_CONFIG_125KBITS(); - // By default we accept all CAN frames. - twai_filter_config_t can_filter_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); - // Note: not using the CAN_GENERAL_CONFIG_DEFAULT macro due to a missing - // cast for CAN_IO_UNUSED. - twai_general_config_t can_general_config = {.mode = TWAI_MODE_NORMAL, - .tx_io = txPin, - .rx_io = rxPin, - .clkout_io = (gpio_num_t)TWAI_IO_UNUSED, - .bus_off_io = (gpio_num_t)TWAI_IO_UNUSED, - .tx_queue_len = (uint32_t)config_can_tx_buffer_size() / 2, - .rx_queue_len = (uint32_t)config_can_rx_buffer_size() / 2, - .alerts_enabled = TWAI_ALERT_NONE, - .clkout_divider = 0}; - - LOG(VERBOSE, - "ESP32-CAN driver configured using RX: %d, TX: %d, RX-Q: %d, " - "TX-Q: %d", - can_general_config.rx_io, can_general_config.tx_io, - can_general_config.rx_queue_len, can_general_config.tx_queue_len); - - ESP_ERROR_CHECK(twai_driver_install( - &can_general_config, &can_timing_config, &can_filter_config)); - - xTaskCreatePinnedToCore(rx_task, "ESP32-CAN RX", OPENMRN_STACK_SIZE, - this, RX_TASK_PRIORITY, &rxTaskHandle_, tskNO_AFFINITY); - xTaskCreatePinnedToCore(tx_task, "ESP32-CAN TX", OPENMRN_STACK_SIZE, - this, TX_TASK_PRIORITY, &txTaskHandle_, tskNO_AFFINITY); - } - - ~Esp32HardwareCanDeprecated() - { - } - - /// Enables the ESP32 CAN driver - virtual void enable() - { - ESP_ERROR_CHECK(twai_start()); - LOG(VERBOSE, "ESP32-CAN driver enabled"); - } - - /// Disables the ESP32 CAN driver - virtual void disable() - { - ESP_ERROR_CHECK(twai_stop()); - LOG(VERBOSE, "ESP32-CAN driver disabled"); - } - -protected: - /// function to try and transmit a message - void tx_msg() override - { - // wake up the tx_task so it can consume any can_frames from txBuf. - xTaskNotifyGive(txTaskHandle_); - } - -private: - /// Default constructor. - Esp32HardwareCanDeprecated(); - - /// Enables/Disables the periodic reporting of CAN bus statistics to the - /// default serial stream. - bool reportStats_ : 1; - /// Set to true if the 'frame dropped' warning is printed. - bool overrunWarningPrinted_ : 1; - - /// Handle for the tx_task that converts and transmits can_frame to the - /// native can driver. - TaskHandle_t txTaskHandle_; - - /// Handle for the rx_task that receives and converts the native can driver - /// frames to can_frame. - TaskHandle_t rxTaskHandle_; - - /// Interval at which to print the ESP32 CAN bus status. - static constexpr TickType_t STATUS_PRINT_INTERVAL = pdMS_TO_TICKS(10000); - - /// Interval to wait between iterations when the bus is recovering, a - /// transmit failure or there is nothing to transmit. - static constexpr TickType_t TX_DEFAULT_DELAY = pdMS_TO_TICKS(250); - - /// Priority to use for the rx_task. This needs to be higher than the - /// tx_task and lower than @ref OPENMRN_TASK_PRIORITY. - static constexpr UBaseType_t RX_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 2; - - /// Priority to use for the tx_task. This should be lower than - /// @ref RX_TASK_PRIORITY and @ref OPENMRN_TASK_PRIORITY. - static constexpr UBaseType_t TX_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 3; - - /// Background task that takes care of the conversion of the @ref can_frame - /// provided by the @ref txBuf into an ESP32 can_message_t which can be - /// processed by the native CAN driver. This task also covers the periodic - /// status reporting and BUS recovery when necessary. - static void tx_task(void *can) - { - /// Get handle to our parent Esp32HardwareCanDeprecated object to - /// access the txBuf. - Esp32HardwareCanDeprecated *parent = - reinterpret_cast(can); - -#if CONFIG_TASK_WDT - // Add this task to the WDT - esp_task_wdt_add(parent->txTaskHandle_); -#endif // CONFIG_TASK_WDT - - /// Tracks the last time that we displayed the CAN driver status. - TickType_t next_status_display_tick_count = 0; - - while (true) - { -#if CONFIG_TASK_WDT - // Feed the watchdog so it doesn't reset the ESP32 - esp_task_wdt_reset(); -#endif // CONFIG_TASK_WDT - - // periodic CAN driver monitoring and reporting, this takes care of - // bus recovery when the CAN driver disables the bus due to error - // conditions exceeding thresholds. - twai_status_info_t status; - twai_get_status_info(&status); - auto current_tick_count = xTaskGetTickCount(); - if (next_status_display_tick_count == 0 || - current_tick_count >= next_status_display_tick_count) - { - next_status_display_tick_count = - current_tick_count + STATUS_PRINT_INTERVAL; - if (parent->reportStats_) - { - LOG(INFO, - "ESP32-CAN: %s rx-q:%d, tx-q:%d, rx-err:%d, tx-err:%d, " - "ovr:%d arb-lost:%d, bus-err:%d, state: %s", - parent->overrunWarningPrinted_ ? "!!OVERRUN!! " : "", - status.msgs_to_rx, status.msgs_to_tx, - status.rx_error_counter, status.tx_error_counter, - parent->overrunCount, status.arb_lost_count, - status.bus_error_count, - ESP32_CAN_STATUS_STRINGS[status.state]); - } - parent->overrunWarningPrinted_ = false; - } - if (status.state == TWAI_STATE_BUS_OFF) - { - // When the bus is OFF we need to initiate recovery, transmit is - // not possible when in this state. - LOG(WARNING, "ESP32-CAN: initiating recovery"); - twai_initiate_recovery(); - continue; - } - else if (status.state == TWAI_STATE_RECOVERING) - { - // when the bus is in recovery mode transmit is not possible. - vTaskDelay(TX_DEFAULT_DELAY); - continue; - } - - // check txBuf for any message to transmit. - unsigned count; - struct can_frame *can_frame = nullptr; - { - AtomicHolder h(parent); - count = parent->txBuf->data_read_pointer(&can_frame); - } - if (!count || !can_frame) - { - // tx Buf empty; wait for tx_msg to be called. - ulTaskNotifyTake(pdTRUE, // clear on exit - TX_DEFAULT_DELAY); - continue; - } - - /// ESP32 native CAN driver frame - twai_message_t msg; - memset(&msg, 0, sizeof(twai_message_t)); - - msg.flags = TWAI_MSG_FLAG_NONE; - msg.identifier = can_frame->can_id; - msg.data_length_code = can_frame->can_dlc; - for (int i = 0; i < can_frame->can_dlc; i++) - { - msg.data[i] = can_frame->data[i]; - } - if (IS_CAN_FRAME_EFF(*can_frame)) - { - msg.flags |= TWAI_MSG_FLAG_EXTD; - } - if (IS_CAN_FRAME_RTR(*can_frame)) - { - msg.flags |= TWAI_MSG_FLAG_RTR; - } - - // Pass the converted CAN frame to the native driver - // for transmit, if the TX queue is full this will - // return ESP_ERR_TIMEOUT which will result in the - // the message being left in txBuf for the next iteration. - // if this call returns ESP_OK we consider the frame as - // transmitted by the driver and remove it from txBuf. - esp_err_t tx_res = twai_transmit(&msg, pdMS_TO_TICKS(100)); - if (tx_res == ESP_OK) - { - LOG(VERBOSE, - "ESP32-CAN-TX OK id:%08x, flags:%04x, dlc:%02d, " - "data:%02x%02x%02x%02x%02x%02x%02x%02x", - msg.identifier, msg.flags, msg.data_length_code, - msg.data[0], msg.data[1], msg.data[2], msg.data[3], - msg.data[4], msg.data[5], msg.data[6], msg.data[7]); - AtomicHolder h(parent); - parent->txBuf->consume(1); - parent->txBuf->signal_condition(); - } - else if (tx_res != ESP_ERR_TIMEOUT) - { - LOG(WARNING, "ESP32-CAN-TX: %s", esp_err_to_name(tx_res)); - vTaskDelay(TX_DEFAULT_DELAY); - } - } // loop on task - } - - /// Background task that takes care of receiving can_message_t objects from - /// the ESP32 native CAN driver, when they are available, converting them to - /// a @ref can_frame and pushing them to the @ref rxBuf. - static void rx_task(void *can) - { - /// Get handle to our parent Esp32HardwareCanDeprecated object to access - /// the rxBuf. - Esp32HardwareCanDeprecated *parent = - reinterpret_cast(can); - -#if CONFIG_TASK_WDT - // Add this task to the WDT - esp_task_wdt_add(parent->rxTaskHandle_); -#endif // CONFIG_TASK_WDT - - while (true) - { -#if CONFIG_TASK_WDT - // Feed the watchdog so it doesn't reset the ESP32 - esp_task_wdt_reset(); -#endif // CONFIG_TASK_WDT - - /// ESP32 native CAN driver frame - twai_message_t msg; - memset(&msg, 0, sizeof(twai_message_t)); - if (twai_receive(&msg, pdMS_TO_TICKS(250)) != ESP_OK) - { - // native CAN driver did not give us a frame. - continue; - } - // we have received a frame from the native CAN driver, verify if - // it is a standard frame, if not we drop it. - if (msg.flags & TWAI_MSG_FLAG_DLC_NON_COMP) - { - LOG(WARNING, - "ESP32-CAN-RX: received non-compliant CAN frame, frame " - "dropped!"); - continue; - } - LOG(VERBOSE, - "ESP32-CAN-RX id:%08x, flags:%04x, dlc:%02d, " - "data:%02x%02x%02x%02x%02x%02x%02x%02x", - msg.identifier, msg.flags, msg.data_length_code, - msg.data[0], msg.data[1], msg.data[2], msg.data[3], - msg.data[4], msg.data[5], msg.data[6], msg.data[7]); - AtomicHolder h(parent); - struct can_frame *can_frame = nullptr; - // verify if we have space in the rxBuf, if not drop the frame and - // record the overrun. - if (!parent->rxBuf->data_write_pointer(&can_frame) || - can_frame == nullptr) - { - if (!parent->overrunWarningPrinted_) - { - parent->overrunWarningPrinted_ = true; - LOG(WARNING, - "ESP32-CAN-RX: buffer overrun, frame dropped!"); - } - parent->overrunCount++; - continue; - } - // we have space in the rxBuf, start conversion - LOG(VERBOSE, "ESP32-CAN-RX: converting to can_frame"); - memset(can_frame, 0, sizeof(struct can_frame)); - can_frame->can_id = msg.identifier; - can_frame->can_dlc = msg.data_length_code; - for (int i = 0; i < msg.data_length_code; i++) - { - can_frame->data[i] = msg.data[i]; - } - if (msg.flags & TWAI_MSG_FLAG_EXTD) - { - SET_CAN_FRAME_EFF(*can_frame); - } - if (msg.flags & TWAI_MSG_FLAG_RTR) - { - SET_CAN_FRAME_RTR(*can_frame); - } - parent->rxBuf->advance(1); - parent->rxBuf->signal_condition(); - } - } - DISALLOW_COPY_AND_ASSIGN(Esp32HardwareCanDeprecated); -}; - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) -/// Esp32HardwareCan has been deprecated due to lack of portability beyond the -/// ESP32. -/// @deprecated Use @ref Esp32HardwareTwai instead. -typedef Esp32HardwareCanDeprecated Esp32HardwareCan __attribute__ (( - deprecated("Esp32HardwareCan has been replaced with Esp32HardwareTwai."))); -#else -/// Esp32HardwareCan will be deprecated once arduino-esp32 2.0.0 has released. -typedef Esp32HardwareCanDeprecated Esp32HardwareCan; -#endif // IDF v4.3+ - -} // namespace openmrn_arduino - -using openmrn_arduino::Esp32HardwareCan; - -#endif /* _FREERTOS_DRIVERS_ARDUINO_ESP32HWCAN_HXX_ */ diff --git a/src/freertos_drivers/esp32/Esp32HardwareI2C.cpp b/src/freertos_drivers/esp32/Esp32HardwareI2C.cpp new file mode 100644 index 000000000..58600b553 --- /dev/null +++ b/src/freertos_drivers/esp32/Esp32HardwareI2C.cpp @@ -0,0 +1,513 @@ +/** \copyright + * Copyright (c) 2023, Mike Dunston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Esp32HardwareI2C.cxx + * + * I2C driver implementation for OpenMRN. This leverages the ESP-IDF I2C driver + * implementation and provides a VFS interface. + * + * @author Mike Dunston + * @date 8 Feb 2023 + */ + +#if defined(ESP_PLATFORM) + +#include "Esp32HardwareI2C.hxx" +// stropts.h must be included before i2c.h +#include "stropts.h" +#include "i2c.h" +#include "i2c-dev.h" +#include "sdkconfig.h" +#include "utils/format_utils.hxx" +#include "utils/logging.h" +#include "utils/StringPrintf.hxx" + +#include + +#if CONFIG_VFS_SUPPORT_TERMIOS +// remove defines added by arduino-esp32 core/esp32/binary.h which are +// duplicated in sys/termios.h which may be included by esp_vfs.h +#undef B110 +#undef B1000000 +#endif // CONFIG_VFS_SUPPORT_TERMIOS +#include +#include +#include + +namespace openmrn_arduino +{ + +extern "C" +{ + +/// VFS adapter for write(fd, buf, size) +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor being written to. +/// @param buf is the buffer containing the data to be written. +/// @param size is the size of the buffer. +/// @return number of bytes written or -1 if there is the write would be a +/// blocking operation. +static ssize_t i2c_vfs_write(void *ctx, int fd, const void *buf, size_t size) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->write(fd, buf, size); +} + +/// VFS adapter for read(fd, buf, size) +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor being read from. +/// @param buf is the buffer to write into. +/// @param size is the size of the buffer. +/// @return number of bytes read or -1 if there is the read would be a +/// blocking operation. +static ssize_t i2c_vfs_read(void *ctx, int fd, void *buf, size_t size) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->read(fd, buf, size); +} + +/// VFS adapter for open(path, flags, mode). +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param path is the path to the file being opened. +/// @param flags are the flags to use for opened file. +/// @param mode is the mode to use for the opened file. +/// +/// When this method is invoked it will enable the TWAI driver and start the +/// periodic timer used for RX/TX of frame data. +/// +/// @return 0 upon success, -1 upon failure with errno containing the cause. +static int i2c_vfs_open(void *ctx, const char *path, int flags, int mode) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->open(path, flags, mode); +} + +/// VFS adapter for close(fd). +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor to close. +/// +/// When this method is invoked it will disable the TWAI driver and stop the +/// periodic timer used for RX/TX of frame data if it is running. +/// +/// @return zero upon success, negative value with errno for failure. +static int i2c_vfs_close(void *ctx, int fd) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->close(fd); +} + +/// VFS adapter for ioctl. +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor to operate on. +/// @param cmd is the command to execute. +/// @param args is the args for the command. +/// +/// @return zero upon success, negative value with errno for failure. +static int i2c_vfs_ioctl(void *ctx, int fd, int cmd, va_list args) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->ioctl(fd, cmd, args); +} + +/// VFS adapter for fcntl(fd, cmd, arg). +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd to operate on. +/// @param cmd to be executed. +/// @param arg arg to be used for the operation. +/// +/// This method is currently a NO-OP. +/// +/// @return zero upon success, negative value with errno for failure. +static int i2c_vfs_fcntl(void *ctx, int fd, int cmd, int arg) +{ + HASSERT(ctx != NULL); + return 0; +} + +} // extern "C" + +Esp32HardwareI2C::Esp32HardwareI2C(const char * const path) + : path_(path) +{ +} + +Esp32HardwareI2C::~Esp32HardwareI2C() +{ + for (size_t idx = 0; idx < SOC_I2C_NUM; idx++) + { + if (i2cInitialized_[idx]) + { + ESP_ERROR_CHECK(i2c_driver_delete(static_cast(idx))); + } + } + if (vfsInitialized_) + { + ESP_ERROR_CHECK(esp_vfs_unregister(path_)); + } +} + +void Esp32HardwareI2C::hw_init(const gpio_num_t sda, const gpio_num_t scl, + const uint32_t bus_speed, const i2c_port_t port) +{ + if (!i2cInitialized_[port]) + { + i2c_config_t i2c_config = {}; + i2c_config.mode = I2C_MODE_MASTER; + i2c_config.sda_io_num = sda; + i2c_config.sda_pullup_en = GPIO_PULLUP_ENABLE; + i2c_config.scl_io_num = scl; + i2c_config.scl_pullup_en = GPIO_PULLUP_ENABLE; + i2c_config.master.clk_speed = bus_speed; + + LOG(INFO, + "[I2C] Initializing I2C%d using SDA:%d, SCL:%d, " + "bus-speed: %" PRIu32, port, sda, scl, bus_speed); + ESP_ERROR_CHECK(i2c_param_config(port, &i2c_config)); + ESP_ERROR_CHECK(i2c_driver_install(port, I2C_MODE_MASTER, + I2C_SLAVE_RX_BUF_SIZE, I2C_SLAVE_TX_BUF_SIZE, I2C_ISR_FLAGS)); + + i2cInitialized_[port] = true; + } + else + { + LOG_ERROR("[I2C] I2C%d has already been initialized!", port); + } + + if (!vfsInitialized_) + { + esp_vfs_t vfs = {}; + vfs.write_p = i2c_vfs_write; + vfs.read_p = i2c_vfs_read; + vfs.open_p = i2c_vfs_open; + vfs.close_p = i2c_vfs_close; + vfs.fcntl_p = i2c_vfs_fcntl; + vfs.ioctl_p = i2c_vfs_ioctl; + vfs.flags = ESP_VFS_FLAG_CONTEXT_PTR; + ESP_ERROR_CHECK(esp_vfs_register(path_, &vfs, this)); + + vfsInitialized_ = true; + } +} + +void Esp32HardwareI2C::scan(const i2c_port_t port) +{ + // Scan the I2C bus and dump the output of devices that respond + std::string scanresults = + " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n" + "00: "; + scanresults.reserve(256); + + HASSERT(i2cInitialized_[port]); + for (uint8_t addr = 0; addr < 0x7F; addr++) + { + if (addr % 16 == 0) + { + scanresults.append(StringPrintf("\n%02x: ", addr)); + } + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(port, cmd, I2C_SCAN_TIMEOUT); + i2c_cmd_link_delete(cmd); + if (ret == ESP_OK) + { + scanresults.append(StringPrintf("%02x ", addr)); + } + else if (ret == ESP_ERR_TIMEOUT) + { + scanresults.append("?? "); + } + else + { + scanresults.append("-- "); + } + } + LOG(INFO, scanresults.c_str()); +} + +ssize_t Esp32HardwareI2C::write(int fd, const void *buf, size_t size) +{ + uint8_t address; + i2c_port_t port; + + { + AtomicHolder h(this); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + + if (entry == devices_.end()) + { + // file handle not found, return an error. + errno = EBADF; + return -EBADF; + } + else if (entry->fd < 0) + { + // no address has been defined for this file handle, return an error. + errno = EINVAL; + return -EINVAL; + } + address = entry->address; + port = entry->port; + } + + esp_err_t res = ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_write_to_device(port, address, (uint8_t *)buf, size, + I2C_OP_TIMEOUT)); + + if (res == ESP_ERR_TIMEOUT) + { + return -ETIMEDOUT; + } + else if (res == ESP_ERR_INVALID_STATE) + { + return -EINVAL; + } + else if (res != ESP_OK) + { + return -EIO; + } + return size; +} + +ssize_t Esp32HardwareI2C::read(int fd, void *buf, size_t size) +{ + uint8_t address; + i2c_port_t port; + + { + AtomicHolder h(this); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + + if (entry == devices_.end()) + { + // file handle not found, return an error. + errno = EBADF; + return -EBADF; + } + else if (entry->fd < 0) + { + // no address has been defined for this file handle, return an error. + errno = EINVAL; + return -EINVAL; + } + address = entry->address; + port = entry->port; + } + + + esp_err_t res = ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_read_from_device(port, address, (uint8_t *)buf, size, + I2C_OP_TIMEOUT)); + + if (res == ESP_ERR_TIMEOUT) + { + return -ETIMEDOUT; + } + else if (res == ESP_ERR_INVALID_STATE) + { + return -EINVAL; + } + else if (res != ESP_OK) + { + return -EIO; + } + return size; +} + +int Esp32HardwareI2C::open(const char *path, int flags, int mode) +{ + std::string path_str = path; + i2c_device_t new_dev = + { + .port = I2C_NUM_0, + .address = -1, + .fd = 0, + }; + + if (path_str.back() == '0') + { + new_dev.port = I2C_NUM_0; + } +#if SOC_I2C_NUM > 1 + else if (path_str.back() == '1') + { + new_dev.port = I2C_NUM_1; + } +#endif // SOC_I2C_NUM > 1 + else + { + LOG_ERROR("[I2C] Unsupported I2C path: %s", path); + return -ENOENT; + } + + if (!i2cInitialized_[new_dev.port]) + { + LOG_ERROR("[I2C] Uninitialized I2C path: %s", path); + return -ENODEV; + } + + // scan existing devices to find a unique file handle number to return to + // the caller. The file handle starts with zero and is set to the maximum + // file handle found plus one. When a file handle is reclaimed there will + // be gaps in the file handles which will not be reclaimed until entries + // with higher file handle numbers are also reclaimed. + { + AtomicHolder h(this); + + if (!devices_.empty()) + { + for (auto &entry: devices_) + { + if (entry.fd >= new_dev.fd) + { + new_dev.fd = entry.fd + 1; + } + } + } + devices_.push_back(new_dev); + } + + LOG(INFO, "[I2C] Using fd: %d (I2C%d) for %s", new_dev.fd, new_dev.port, + path); + + return new_dev.fd; +} + +int Esp32HardwareI2C::close(int fd) +{ + AtomicHolder h(this); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + + // only delete the device if has been found. + if (entry != devices_.end()) + { + devices_.erase(entry); + } + return 0; +} + +int Esp32HardwareI2C::ioctl(int fd, int cmd, va_list args) +{ + AtomicHolder h(this); + HASSERT(IOC_TYPE(cmd) == I2C_MAGIC); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + if (entry == devices_.end()) + { + errno = EBADF; + return -EBADF; + } + + switch(static_cast(cmd)) + { + default: + return -EINVAL; + case I2C_SLAVE: + entry->address = static_cast(va_arg(args, int)); + return 0; + case I2C_RDWR: + struct i2c_rdwr_ioctl_data *data = + reinterpret_cast(va_arg(args, uintptr_t)); + return transfer_messages(entry->port, data->msgs, data->nmsgs); + } + + return 0; +} + +int Esp32HardwareI2C::transfer_messages( + const i2c_port_t port, struct i2c_msg *msgs, int num) +{ + int total_len = 0; + esp_err_t res = ESP_OK; + + for (int idx = 0; idx < num; idx++) + { + struct i2c_msg *msg = msgs + idx; + if (msg->flags & I2C_M_RD) + { + res = + ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_read_from_device(port, msg->addr, + (uint8_t *)msg->buf, msg->len, I2C_OP_TIMEOUT)); + } + else + { + res = + ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_write_to_device(port, msg->addr, + (uint8_t *)msg->buf, msg->len, I2C_OP_TIMEOUT)); + } + if (res == ESP_ERR_TIMEOUT) + { + return -ETIMEDOUT; + } + else if (res == ESP_ERR_INVALID_STATE) + { + return -EINVAL; + } + else if (res != ESP_OK) + { + return -EIO; + } + total_len += msg->len; + } + return total_len; +} + +} // namespace openmrn_arduino + +#endif // ESP_PLATFORM \ No newline at end of file diff --git a/src/freertos_drivers/esp32/Esp32HardwareI2C.hxx b/src/freertos_drivers/esp32/Esp32HardwareI2C.hxx new file mode 100644 index 000000000..e4d47b2c1 --- /dev/null +++ b/src/freertos_drivers/esp32/Esp32HardwareI2C.hxx @@ -0,0 +1,230 @@ + +/** \copyright + * Copyright (c) 2023, Mike Dunston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Esp32HardwareI2C.hxx + * + * I2C driver implementation for OpenMRN. This leverages the ESP-IDF I2C driver + * implementation and provides a VFS interface. + * + * @author Mike Dunston + * @date 8 Feb 2023 + */ + +#ifndef _FREERTOS_DRIVERS_ESP32_ESP32HARDWAREI2C_HXX_ +#define _FREERTOS_DRIVERS_ESP32_ESP32HARDWAREI2C_HXX_ + +#include "i2c.h" +#include "i2c-dev.h" +#include +#include +#include +#include +#include + +namespace openmrn_arduino +{ + +/// The ESP32 has one or two built-in I2C controller interfaces, this code +/// provides a VFS interface that can be used by various I2C drivers to access +/// the underlying I2C bus. +/// +/// Only one instance of this class should be created in the application so the +/// memory overhead is minimized and performance of the VFS minimally impacted. +/// +/// Example usage (scanner): +/// ``` +/// Esp32HardwareI2C i2c; +/// +/// void setup() +/// { +/// i2c.hw_init(GPIO_NUM_4, GPIO_NUM_5, 100000); +/// i2c.scan(); +/// } +/// ``` +/// +/// Example usage (MCP23017): +/// ``` +/// Esp32HardwareI2C i2c; +/// Executor<1> io_executor("io_thread", 1, 1300); +/// MCP23017 expander(&io_executor, 0, 0, 0); +/// +/// void setup() +/// { +/// i2c.hw_init(GPIO_NUM_4, GPIO_NUM_5, 100000); +/// expander.init("/dev/i2c/0"); +/// ... +/// } +/// ``` +class Esp32HardwareI2C : private Atomic +{ +public: + /// Constructor. + /// + /// @param path Base path to use for I2C drivers. + Esp32HardwareI2C(const char * const path = "/dev/i2c"); + + /// Destructor. + ~Esp32HardwareI2C(); + + /// Initializes the underlying I2C controller hardware and VFS interface. + /// + /// @param sda GPIO pin to use for I2C data transfer. + /// @param scl GPIO pin to use for I2C clock. + /// @param bus_speed I2C clock frequency. + /// @param port I2C controller to initialize. + /// + /// For hardware which supports more than one I2C controller, this method + /// must be called once per controller being used. + /// + /// NOTE: This must be called prior to usage of any other methods for each + /// I2C controller being used. + void hw_init(const gpio_num_t sda, const gpio_num_t scl, + const uint32_t bus_speed, const i2c_port_t port = I2C_NUM_0); + + /// Utility method to perform a scan for all devices on the I2C bus. + /// + /// @param port Hardware I2C controller port number to use for scanning. + /// + /// NOTE: The hw_init method must have been run prior to calling this + /// method. Failure to do so will result in an error being raised. + void scan(const i2c_port_t port); + + /// VFS implementation of write(fd, buf, size) + /// + /// @param fd is the file descriptor being written to. + /// @param buf is the buffer containing the data to be written. + /// @param size is the size of the buffer. + /// + /// @return number of bytes written or -1 if there is the write would be a + /// blocking operation. + /// + /// NOTE: The provided fd is used internally to determine which I2C + /// controller should be used and the address to write the data to. If the + /// address has not been set + ssize_t write(int fd, const void *buf, size_t size); + + /// VFS implementation of read(fd, buf, size) + /// + /// @param fd is the file descriptor being read from. + /// @param buf is the buffer to write into. + /// @param size is the size of the buffer. + /// @return number of bytes read or -1 if there is the read would be a + /// blocking operation. + ssize_t read(int fd, void *buf, size_t size); + + /// VFS implementation of open(path, flags, mode). + /// + /// @param path is the path to the file being opened. + /// @param flags are the flags to use for opened file. + /// @param mode is the mode to use for the opened file. + /// + /// When this method is invoked it will enable the TWAI driver and start the + /// periodic timer used for RX/TX of frame data. + /// + /// @return 0 upon success, -1 upon failure with errno containing the cause. + int open(const char *path, int flags, int mode); + + /// VFS implementation of close(fd). + /// + /// @param fd is the file descriptor to close. + /// + /// When this method is invoked it will disable the TWAI driver and stop the + /// periodic timer used for RX/TX of frame data if it is running. + /// + /// @return zero upon success, negative value with errno for failure. + int close(int fd); + + /// VFS implementation of ioctl. + /// + /// @param fd is the file descriptor to operate on. + /// @param cmd is the command to execute. + /// @param args is the args for the command. + /// + /// @return zero upon success, negative value with errno for failure. + int ioctl(int fd, int cmd, va_list args); + +private: + /// VFS Mount point. + const char * const path_; + + /// Timeout to use for I2C operations, default is one second. + static constexpr TickType_t I2C_OP_TIMEOUT = pdMS_TO_TICKS(1000); + + /// Timeout to use for I2C device scanning, default is 50 milliseconds. + static constexpr TickType_t I2C_SCAN_TIMEOUT = pdMS_TO_TICKS(50); + + /// ISR flags to use for I2C, this defaults to allowing usage of a shared + /// interupt. + static constexpr int I2C_ISR_FLAGS = ESP_INTR_FLAG_SHARED; + + /// I2C "Slave" RX buffer size, we do not use/support this feature so it is + /// set to zero. + static constexpr size_t I2C_SLAVE_RX_BUF_SIZE = 0; + + /// I2C "Slave" TX buffer size, we do not use/support this feature so it is + /// set to zero. + static constexpr size_t I2C_SLAVE_TX_BUF_SIZE = 0; + + /// Tracking structure used to map file handles to an I2C controller and + /// address. + struct i2c_device_t + { + /// I2C Controller that this file handle will use. + i2c_port_t port; + + /// I2C address to use when greater than zero. + int address; + + /// Assigned file handle for this entry. + int fd; + }; + + /// Collection of I2C devices that have been opened. + std::vector devices_; + + /// Internal tracking for initialization of the underlying I2C hardware. + bool i2cInitialized_[SOC_I2C_NUM]; + + /// Internal tracking for the VFS adapter layer. + bool vfsInitialized_{false}; + + /// Transfers multiple payloads to I2C devices. + /// + /// @param port Underlying @ref i2c_port_t to use for I2C transfers. + /// @param msgs payloads to be transfered, this includes the target device + /// address, data to transfer and length of data. + /// @param num Number of transfers to perform. + /// + /// @return Number of bytes transfered or an error code (negative value). + int transfer_messages(const i2c_port_t port, struct i2c_msg *msgs, int num); +}; + +}; // namespace openmrn_arduino + +using openmrn_arduino::Esp32HardwareI2C; + +#endif // _FREERTOS_DRIVERS_ESP32_ESP32HARDWAREI2C_HXX_ \ No newline at end of file diff --git a/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx b/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx index 029af31c0..04d5c6b82 100644 --- a/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx +++ b/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx @@ -35,14 +35,10 @@ * @date 1 May 2021 */ -// Ensure we only compile this code for the ESP32 family of MCUs and that the -// ESP-IDF version is supported for this code. -#if defined(ESP32) +// Ensure we only compile this code for the ESP32 family of MCUs. +#if defined(ESP_PLATFORM) #include "sdkconfig.h" -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) #if CONFIG_VFS_SUPPORT_TERMIOS // remove defines added by arduino-esp32 core/esp32/binary.h which are @@ -53,11 +49,12 @@ #include #include -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) +#include + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,1,0) +#include +#endif #include -#else // IDF v4.x (or earlier) -#include -#endif // IDF v5+ #include #include #include @@ -610,7 +607,7 @@ static inline uint32_t twai_rx_frames() uint32_t rx_ready_count = twai_hal_get_rx_msg_count(&twai.context); struct can_frame *can_frame = nullptr; uint32_t rx_count = 0; - ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-ready-count: %d", rx_ready_count); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-ready-count: %" PRIu32, rx_ready_count); for (uint32_t idx = 0; idx < rx_ready_count; idx++) { twai_hal_frame_t frame; @@ -620,7 +617,7 @@ static inline uint32_t twai_rx_frames() { // DLC is longer than supported, discard the frame. twai.stats.rx_discard++; - ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-discard:%d", + ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-discard:%" PRIu32, twai.stats.rx_discard); } else if (twai.rx_buf->data_write_pointer(&can_frame)) @@ -652,13 +649,13 @@ static inline uint32_t twai_rx_frames() else { twai.stats.rx_missed++; - ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-missed:%d", + ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-missed:%" PRIu32, twai.stats.rx_missed); } } else { - ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-overrun"); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-overrun"); // If the SOC does not support automatic clearing of the RX FIFO we need to // handle it here and break out of the loop. #ifndef SOC_TWAI_SUPPORTS_RX_STATUS @@ -716,7 +713,7 @@ static void twai_isr(void *arg) { BaseType_t wakeup = pdFALSE; uint32_t events = twai_hal_get_events(&twai.context); - ESP_EARLY_LOGV(TWAI_LOG_TAG, "events: %08x", events); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "events: %04" PRIx32, events); #if defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || \ defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT) @@ -788,14 +785,16 @@ static void twai_isr(void *arg) if (events & TWAI_HAL_EVENT_BUS_ERR) { twai.stats.bus_error++; - ESP_EARLY_LOGV(TWAI_LOG_TAG, "bus-error:%d", twai.stats.bus_error); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "bus-error:%" PRIu32, + twai.stats.bus_error); } // Arbitration error detected if (events & TWAI_HAL_EVENT_ARB_LOST) { twai.stats.arb_loss++; - ESP_EARLY_LOGV(TWAI_LOG_TAG, "arb-lost:%d", twai.stats.arb_loss); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "arb-lost:%" PRIu32, + twai.stats.arb_loss); } if (wakeup == pdTRUE) @@ -876,9 +875,11 @@ void* twai_watchdog(void* param) { LOG(INFO, "ESP-TWAI: " - "RX:%d (pending:%zu,overrun:%d,discard:%d,missed:%d,lost:%d) " - "TX:%d (pending:%zu,suc:%d,fail:%d) " - "Bus (arb-loss:%d,err:%d,state:%s)", + "RX:%" PRIu32 " (pending:%zu,overrun:%" PRIu32 + ",discard:%" PRIu32 ",missed:%" PRIu32 ",lost:%" PRIu32 ") " + "TX:%" PRIu32 " (pending:%zu,suc:%" PRIu32 + ",fail:%" PRIu32 ") " + "Bus (arb-err:%" PRIu32 ",err:%" PRIu32 ",state:%s)", twai.stats.rx_processed, twai.rx_buf->pending(), twai.stats.rx_overrun, twai.stats.rx_discard, twai.stats.rx_missed, twai.stats.rx_lost, @@ -1005,12 +1006,46 @@ void Esp32HardwareTwai::hw_init() periph_module_reset(PERIPH_TWAI_MODULE); periph_module_enable(PERIPH_TWAI_MODULE); - HASSERT(twai_hal_init(&twai.context)); + twai_timing_config_t timingCfg = TWAI_TIMING_CONFIG_125KBITS(); twai_filter_config_t filterCfg = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,1,0) + // default clock source if not specified in config. + if (timingCfg.clk_src == 0) + { + timingCfg.clk_src = TWAI_CLK_SRC_DEFAULT; + } + twai_hal_config_t twai_hal_cfg = + { + .controller_id = 0, + .clock_source_hz = 0, + }; + + // retrieve the clock frequency from the SoC + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)timingCfg.clk_src, + ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &twai_hal_cfg.clock_source_hz); + + // BRP validations + uint32_t brp = timingCfg.brp; + if (timingCfg.quanta_resolution_hz) + { + HASSERT(twai_hal_cfg.clock_source_hz % timingCfg.quanta_resolution_hz == 0); + brp = twai_hal_cfg.clock_source_hz / timingCfg.quanta_resolution_hz; + } + HASSERT(twai_ll_check_brp_validation(brp)); + + // Initialize the low level HAL APIs + HASSERT(twai_hal_init(&twai.context, &twai_hal_cfg)); +#else + // Initialize the low level HAL APIs + HASSERT(twai_hal_init(&twai.context)); +#endif // IDF v5.1+ + LOG(VERBOSE, "ESP-TWAI: Initiailizing peripheral"); twai_hal_configure(&twai.context, &timingCfg, &filterCfg, TWAI_DEFAULT_INTERRUPTS, 0); + #if SOC_CPU_CORES_NUM > 1 ESP_ERROR_CHECK( esp_ipc_call_blocking(preferredIsrCore_, esp32_twai_isr_init, nullptr)); @@ -1031,6 +1066,4 @@ void Esp32HardwareTwai::get_driver_stats(esp32_twai_stats_t *stats) } // namespace openmrn_arduino -#endif // IDF v4.3+ - -#endif // ESP32 +#endif // ESP_PLATFORM diff --git a/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx b/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx index b934d6125..957cb8aa2 100644 --- a/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx +++ b/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx @@ -37,12 +37,6 @@ #ifndef _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ #define _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ -#include - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) -#error Esp32HardwareTwai is only supported on ESP-IDF v4.3 and above. -#endif // IDF v4.3+ - #include #include #include @@ -222,4 +216,4 @@ private: using openmrn_arduino::esp32_twai_stats_t; using openmrn_arduino::Esp32HardwareTwai; -#endif // _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ \ No newline at end of file +#endif // _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32Ledc.cxx b/src/freertos_drivers/esp32/Esp32Ledc.cxx index 0d856f064..7dfa35b83 100644 --- a/src/freertos_drivers/esp32/Esp32Ledc.cxx +++ b/src/freertos_drivers/esp32/Esp32Ledc.cxx @@ -33,11 +33,7 @@ */ // Ensure we only compile this code for the ESP32 family of MCUs. -#if defined(ESP32) - -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) +#if defined(ESP_PLATFORM) #include "Esp32Ledc.hxx" @@ -48,6 +44,4 @@ pthread_once_t Esp32Ledc::ledcFadeOnce_ = PTHREAD_ONCE_INIT; } // namespace openmrn_arduino -#endif // IDF v4.3+ - -#endif // ESP32 \ No newline at end of file +#endif // ESP_PLATFORM diff --git a/src/freertos_drivers/esp32/Esp32Ledc.hxx b/src/freertos_drivers/esp32/Esp32Ledc.hxx index 72d5bfff8..a3fb991a8 100644 --- a/src/freertos_drivers/esp32/Esp32Ledc.hxx +++ b/src/freertos_drivers/esp32/Esp32Ledc.hxx @@ -111,7 +111,7 @@ public: { // Ensure the pin count is valid and within range of usable channels. HASSERT(pins_.size() > 0 && - pins_.size() < (LEDC_CHANNEL_MAX - first_channel)); + pins_.size() <= (LEDC_CHANNEL_MAX - first_channel)); memset(&timerConfig_, 0, sizeof(ledc_timer_config_t)); // timerConfig_.speed_mode will be assigned the SOC default mode, which // is either HIGH speed or LOW speed depending on the hardware support. @@ -132,7 +132,8 @@ public: void hw_init() { LOG(INFO, - "[Esp32Ledc:%d] Configuring timer (resolution:%d, frequency:%d)", + "[Esp32Ledc:%d] Configuring timer (resolution:%d, frequency:%" + PRIu32 ")", timerConfig_.timer_num, (1 << (uint8_t)timerConfig_.duty_resolution) - 1, timerConfig_.freq_hz); @@ -277,6 +278,7 @@ private: { ESP_ERROR_CHECK( ledc_set_duty(timerConfig_.speed_mode, channel, counts)); + ESP_ERROR_CHECK(ledc_update_duty(timerConfig_.speed_mode, channel)); } /// Gets the duty cycle. @@ -323,4 +325,4 @@ private: using openmrn_arduino::Esp32Ledc; -#endif // _DRIVERS_ESP32LEDC_HXX_ \ No newline at end of file +#endif // _DRIVERS_ESP32LEDC_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32SocInfo.cxx b/src/freertos_drivers/esp32/Esp32SocInfo.cxx index e56611b2f..db4a50cc6 100644 --- a/src/freertos_drivers/esp32/Esp32SocInfo.cxx +++ b/src/freertos_drivers/esp32/Esp32SocInfo.cxx @@ -33,16 +33,13 @@ * @date 4 May 2021 */ -#if defined(ESP32) +#if defined(ESP_PLATFORM) #include "freertos_drivers/esp32/Esp32SocInfo.hxx" #include "utils/logging.h" #include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,4,0) #include -#endif // IDF v4.4+ namespace openmrn_arduino { @@ -265,25 +262,14 @@ uint8_t Esp32SocInfo::print_soc_info() chip_info.features & CHIP_FEATURE_BLE ? "Yes" : "No", chip_info.features & CHIP_FEATURE_BT ? "Yes" : "No"); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) LOG(INFO, "[SoC] Heap: %.2fkB / %.2fkB", heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024.0f, heap_caps_get_total_size(MALLOC_CAP_INTERNAL) / 1024.0f); -#if CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM +#if CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM || CONFIG_SPIRAM LOG(INFO, "[SoC] PSRAM: %.2fkB / %.2fkB", heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024.0f, heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024.0f); -#endif // CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM - -#else // NOT IDF v4.3+ - LOG(INFO, "[SoC] Free Heap: %.2fkB", - heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024.0f); -#if CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM - LOG(INFO, "[SoC] Free PSRAM: %.2fkB", - heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024.0f); -#endif // CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM - -#endif // IDF v4.3+ +#endif // CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM || CONFIG_SPIRAM LOG(INFO, "[SoC] App running from partition: %s", esp_ota_get_running_partition()->label); @@ -297,4 +283,4 @@ uint8_t Esp32SocInfo::print_soc_info() } // namespace openmrn_arduino -#endif // ESP32 \ No newline at end of file +#endif // ESP_PLATFORM diff --git a/src/freertos_drivers/esp32/Esp32SocInfo.hxx b/src/freertos_drivers/esp32/Esp32SocInfo.hxx index e683b8309..f55cc260b 100644 --- a/src/freertos_drivers/esp32/Esp32SocInfo.hxx +++ b/src/freertos_drivers/esp32/Esp32SocInfo.hxx @@ -36,17 +36,12 @@ #include -#if defined(ESP32) +#if defined(ESP_PLATFORM) #include "sdkconfig.h" -#include #if defined(CONFIG_IDF_TARGET_ESP32) -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) #include -#else -#include -#endif // IDF v4.3+ #elif defined(CONFIG_IDF_TARGET_ESP32S2) #include #elif defined(CONFIG_IDF_TARGET_ESP32S3) @@ -76,6 +71,6 @@ public: using openmrn_arduino::Esp32SocInfo; -#endif // ESP32 +#endif // ESP_PLATFORM -#endif // _FREERTOS_DRIVERS_ESP32_ESP32SOCINFO_HXX_ \ No newline at end of file +#endif // _FREERTOS_DRIVERS_ESP32_ESP32SOCINFO_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx index 9bd94acbd..5471345f8 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx +++ b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx @@ -32,8 +32,8 @@ * @date 4 February 2019 */ -// Ensure we only compile this code on ESP32 MCUs -#ifdef ESP32 +// Ensure we only compile this code for ESP32 MCUs +#ifdef ESP_PLATFORM #include "Esp32WiFiManager.hxx" #include "openlcb/SimpleStack.hxx" @@ -53,7 +53,6 @@ // ESP-IDF v4+ has a slightly different directory structure to previous // versions. -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) #include #include #include @@ -67,12 +66,7 @@ #else // default to ESP32 #include #endif // CONFIG_IDF_TARGET - -#else // ESP-IDF v3.x -#include -#include -#include -#endif // ESP_IDF_VERSION +#include using openlcb::NodeID; using openlcb::SimpleCanStackBase; @@ -317,17 +311,12 @@ Esp32WiFiManager::Esp32WiFiManager(const char *station_ssid Esp32WiFiManager::~Esp32WiFiManager() { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) // Remove our event listeners from the event loop, note that we do not stop // the event loop. esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID , &Esp32WiFiManager::process_idf_event); esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID , &Esp32WiFiManager::process_idf_event); -#else - // Disconnect from the event loop to prevent a possible null deref. - esp_event_loop_set_cb(nullptr, nullptr); -#endif // IDF v4.1+ // shutdown the background task executor. executor_.shutdown(); @@ -405,8 +394,8 @@ ConfigUpdateListener::UpdateAction Esp32WiFiManager::apply_configuration( // Calculate CRC32 from the loaded buffer. uint32_t configCrc32 = crc32_le(0, crcbuf.get(), cfg_.size()); - LOG(VERBOSE, "existing config CRC32:%d, new CRC32:%d", configCrc32_, - configCrc32); + LOG(VERBOSE, "existing config CRC32:%" PRIu32 ", new CRC32:%" PRIu32, + configCrc32_, configCrc32); if (initial_load) { @@ -480,12 +469,11 @@ void Esp32WiFiManager::factory_reset(int fd) CDI_FACTORY_RESET(cfg_.uplink().reconnect); } -#if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) void Esp32WiFiManager::process_idf_event(void *arg, esp_event_base_t event_base , int32_t event_id, void *event_data) { - LOG(VERBOSE, "Esp32WiFiManager::process_idf_event(%s, %d, %p)", event_base - , event_id, event_data); + LOG(VERBOSE, "Esp32WiFiManager::process_idf_event(%s, %" PRIi32 ", %p)", + event_base, event_id, event_data); if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { Singleton::instance()->on_station_started(); @@ -540,65 +528,6 @@ void Esp32WiFiManager::process_idf_event(void *arg, esp_event_base_t event_base } } -#else -// Processes a WiFi system event -esp_err_t Esp32WiFiManager::process_wifi_event(void *ctx, system_event_t *event) -{ - LOG(VERBOSE, "Esp32WiFiManager::process_wifi_event(%d)", event->event_id); - - if (event->event_id == SYSTEM_EVENT_STA_START) - { - Singleton::instance()->on_station_started(); - } - else if (event->event_id == SYSTEM_EVENT_STA_CONNECTED) - { - Singleton::instance()->on_station_connected(); - } - else if (event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) - { - Singleton::instance()->on_station_disconnected( - event->event_info.disconnected.reason); - } - else if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) - { - Singleton::instance()->on_station_ip_assigned( - htonl(event->event_info.got_ip.ip_info.ip.addr)); - } - else if (event->event_id == SYSTEM_EVENT_STA_LOST_IP) - { - Singleton::instance()->on_station_ip_lost(); - } - else if (event->event_id == SYSTEM_EVENT_AP_START) - { - Singleton::instance()->on_softap_start(); - } - else if (event->event_id == SYSTEM_EVENT_AP_STOP) - { - Singleton::instance()->on_softap_stop(); - } - else if (event->event_id == SYSTEM_EVENT_AP_STACONNECTED) - { - auto sta_data = event->event_info.sta_connected; - Singleton::instance()->on_softap_station_connected( - sta_data.mac, sta_data.aid); - } - else if (event->event_id == SYSTEM_EVENT_AP_STADISCONNECTED) - { - auto sta_data = event->event_info.sta_connected; - Singleton::instance()->on_softap_station_disconnected( - sta_data.mac, sta_data.aid); - } - else if (event->event_id == SYSTEM_EVENT_SCAN_DONE) - { - auto scan_data = event->event_info.scan_done; - Singleton::instance()->on_wifi_scan_completed( - scan_data.status, scan_data.number); - } - - return ESP_OK; -} -#endif - // Adds a callback which will be called when the network is up. void Esp32WiFiManager::register_network_up_callback( esp_network_up_callback_t callback) @@ -641,20 +570,27 @@ void Esp32WiFiManager::stop_hub() // Creates a hub listener for this node after loading configuration details. void Esp32WiFiManager::start_hub() { - hubServiceName_ = cfg_.hub().service_name().read(configFd_); - uint16_t hub_port = CDI_READ_TRIMMED(cfg_.hub().port, configFd_); - - LOG(INFO, "[Hub] Starting TCP/IP listener on port %d", hub_port); auto stack = static_cast(stack_); - stack->start_tcp_hub_server(hub_port); - auto hub = stack->get_tcp_hub_server(); - // wait for the hub to complete it's startup tasks - while (!hub->is_started()) + // Check if we already have a GC Hub running or not, if we do then we can + // skip creating one. It would be best to validate the port in use but it + // is not exposed by GcHub at this time. + if (!stack->get_tcp_hub_server()) { - usleep(HUB_STARTUP_DELAY_USEC); + hubServiceName_ = cfg_.hub().service_name().read(configFd_); + uint16_t hub_port = CDI_READ_TRIMMED(cfg_.hub().port, configFd_); + LOG(INFO, "[HUB] Starting TCP/IP listener on port %" PRIu16, hub_port); + stack->start_tcp_hub_server(hub_port); + auto hub = stack->get_tcp_hub_server(); + + // wait for the hub to complete it's startup tasks + while (!hub->is_started()) + { + usleep(HUB_STARTUP_DELAY_USEC); + } + + mdns_publish(hubServiceName_, hub_port); } - mdns_publish(hubServiceName_, hub_port); } // Disconnects and shuts down the uplink connector socket if running. @@ -807,9 +743,9 @@ void Esp32WiFiManager::mdns_publish(string service, const uint16_t port) split_mdns_service_name(&service_name, &protocol_name); esp_err_t res = mdns_service_add( NULL, service_name.c_str(), protocol_name.c_str(), port, NULL, 0); - LOG(VERBOSE, "[mDNS] mdns_service_add(%s.%s:%d):%s." - , service_name.c_str(), protocol_name.c_str(), port - , esp_err_to_name(res)); + LOG(VERBOSE, "[mDNS] mdns_service_add(%s.%s:%" PRIu16 "):%s.", + service_name.c_str(), protocol_name.c_str(), port, + esp_err_to_name(res)); // ESP_FAIL will be triggered if there is a timeout during publish of // the new mDNS entry. The mDNS task runs at a very low priority on the // PRO_CPU which is also where the OpenMRN Executor runs from which can @@ -833,12 +769,12 @@ void Esp32WiFiManager::mdns_publish(string service, const uint16_t port) } else if (res == ESP_OK) { - LOG(INFO, "[mDNS] Advertising %s.%s:%d.", service_name.c_str(), - protocol_name.c_str(), port); + LOG(INFO, "[mDNS] Advertising %s.%s:%" PRIu16 ".", + service_name.c_str(), protocol_name.c_str(), port); } else { - LOG_ERROR("[mDNS] Failed to publish:%s.%s:%d", + LOG_ERROR("[mDNS] Failed to publish:%s.%s:%" PRIu16, service_name.c_str(), protocol_name.c_str(), port); Singleton::instance()->mdns_publish( service, port); @@ -913,12 +849,7 @@ void Esp32WiFiManager::on_station_started() // so that it shows up with the generated hostname instead of // the default "Espressif". LOG(INFO, "[Station] Setting hostname to \"%s\".", hostname_.c_str()); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) esp_netif_set_hostname(espNetIfaces_[STATION_INTERFACE], hostname_.c_str()); -#else - ESP_ERROR_CHECK(tcpip_adapter_set_hostname( - TCPIP_ADAPTER_IF_STA, hostname_.c_str())); -#endif uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); LOG(INFO, "[Station] MAC Address:%s", mac_to_string(mac).c_str()); @@ -926,11 +857,7 @@ void Esp32WiFiManager::on_station_started() // Start the DHCP service before connecting so it hooks into // the flow early and provisions the IP automatically. LOG(INFO, "[Station] Starting DHCP Client."); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) ESP_ERROR_CHECK(esp_netif_dhcpc_start(espNetIfaces_[STATION_INTERFACE])); -#else - ESP_ERROR_CHECK(tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA)); -#endif // IDF v4.1+ LOG(INFO, "[Station] Connecting to SSID:%s.", ssid_.c_str()); // Start the SSID connection process. @@ -1077,7 +1004,6 @@ void Esp32WiFiManager::on_softap_start() esp_wifi_get_mac(WIFI_IF_AP, mac); LOG(INFO, "[SoftAP] MAC Address:%s", mac_to_string(mac).c_str()); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) // Set the generated hostname prior to connecting to the SSID // so that it shows up with the generated hostname instead of // the default "Espressif". @@ -1091,21 +1017,6 @@ void Esp32WiFiManager::on_softap_start() ESP_ERROR_CHECK( esp_netif_get_ip_info(espNetIfaces_[SOFTAP_INTERFACE], &ip_info)); ip_address = ntohl(ip4_addr_get_u32(&ip_info.ip)); -#else - // Set the generated hostname prior to connecting to the SSID - // so that it shows up with the generated hostname instead of - // the default "Espressif". - LOG(INFO, "[SoftAP] Setting hostname to \"%s\".", hostname_.c_str()); - ESP_ERROR_CHECK(tcpip_adapter_set_hostname( - TCPIP_ADAPTER_IF_AP, hostname_.c_str())); - - // fetch the IP address from the adapter since it defaults to - // 192.168.4.1 but can be altered via sdkconfig. - tcpip_adapter_ip_info_t ip_info; - ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP - , &ip_info)); - ip_address = ntohl(ip4_addr_get_u32(&ip_info.ip)); -#endif // IDF v4.1+ LOG(INFO, "[SoftAP] IP address:%s", ipv4_to_string(ip_address).c_str()); @@ -1180,7 +1091,7 @@ void Esp32WiFiManager::on_wifi_scan_completed(uint32_t status, uint8_t count) { uint16_t num_found = count; esp_wifi_scan_get_ap_num(&num_found); - LOG(VERBOSE, "[WiFi] %d SSIDs found via scan", num_found); + LOG(VERBOSE, "[WiFi] %" PRIu16 " SSIDs found via scan", num_found); ssidScanResults_.resize(num_found); esp_wifi_scan_get_ap_records(&num_found, ssidScanResults_.data()); #if LOGLEVEL >= VERBOSE @@ -1226,12 +1137,17 @@ void Esp32WiFiManager::configure_sntp() { sntpConfigured_ = true; LOG(INFO, "[SNTP] Polling %s for time updates", sntpServer_.c_str()); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,1,0) + esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); + esp_sntp_setservername(0, sntpServer_.c_str()); + sntp_set_time_sync_notification_cb(sntp_update_received); + esp_sntp_init(); +#else sntp_setoperatingmode(SNTP_OPMODE_POLL); - // IDF v3.3 does not offer const correctness so we need to drop const - // when setting the hostname for SNTP. - sntp_setservername(0, const_cast(sntpServer_.c_str())); + sntp_setservername(0, sntpServer_.c_str()); sntp_set_time_sync_notification_cb(sntp_update_received); sntp_init(); +#endif // IDF v5.1+ if (!timeZone_.empty()) { @@ -1296,7 +1212,6 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::noop() StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::init_interface() { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) // create default interfaces for station and SoftAP, ethernet is not used // today. ESP_ERROR_CHECK(esp_netif_init()); @@ -1323,15 +1238,6 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::init_interface() &Esp32WiFiManager::process_idf_event, nullptr); esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &Esp32WiFiManager::process_idf_event, nullptr); -#else // NOT IDF v4.1+ - // Initialize the TCP/IP adapter stack. - LOG(INFO, "[WiFi] Starting TCP/IP stack"); - tcpip_adapter_init(); - - // Install event loop handler. - ESP_ERROR_CHECK( - esp_event_loop_init(&Esp32WiFiManager::process_wifi_event, nullptr)); -#endif // IDF v4.1+ return yield_and_call(STATE(init_wifi)); } @@ -1419,11 +1325,7 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::configure_station() } LOG(INFO, "[WiFi] Configuring Station (SSID:%s)", conf.sta.ssid); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &conf)); -#else - ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &conf)); -#endif // IDF v4.0+ return yield_and_call(STATE(start_wifi)); } @@ -1478,11 +1380,7 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::configure_softap() } LOG(INFO, "[WiFi] Configuring SoftAP (SSID:%s)", conf.ap.ssid); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &conf)); -#else - ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &conf)); -#endif // IDF v4.0+ // If we are only enabling the SoftAP we can transition to starting the // WiFi stack. @@ -1722,7 +1620,7 @@ int mdns_lookup( if (ipaddr->addr.type == IPADDR_TYPE_V4) { LOG(ESP32_WIFIMGR_MDNS_LOOKUP_LOG_LEVEL, - "[mDNS] Found %s as providing service:%s on port %d.", + "[mDNS] Found %s providing service:%s on port %" PRIu16, res->hostname, service, res->port); inet_addr_from_ip4addr( &sa_in->sin_addr, &ipaddr->addr.u_addr.ip4); @@ -1762,17 +1660,11 @@ int mdns_lookup( /// @return zero for success, -1 for failure. int getifaddrs(struct ifaddrs **ifap) { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) esp_netif_ip_info_t ip_info; -#else - tcpip_adapter_ip_info_t ip_info; -#endif // IDF v5+ /* start with something "safe" in case we bail out early */ *ifap = nullptr; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) - // Lookup the interface by it's internal name assigned by ESP-IDF. esp_netif_t *iface = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); if (iface == nullptr) @@ -1789,17 +1681,6 @@ int getifaddrs(struct ifaddrs **ifap) errno = EADDRNOTAVAIL; return -1; } -#else // IDF v4 (or lower) - if (!tcpip_adapter_is_netif_up(TCPIP_ADAPTER_IF_STA)) - { - // Station TCP/IP interface is not up - errno = ENODEV; - return -1; - } - - // retrieve TCP/IP address from the interface - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); -#endif // IDF v5+ // allocate memory for various pieces of ifaddrs std::unique_ptr ia(new struct ifaddrs); diff --git a/src/freertos_drivers/esp32/Esp32WiFiManager.hxx b/src/freertos_drivers/esp32/Esp32WiFiManager.hxx index a1996c8dd..7ad7dae6d 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiManager.hxx +++ b/src/freertos_drivers/esp32/Esp32WiFiManager.hxx @@ -46,16 +46,10 @@ #include "utils/macros.h" #include "utils/Singleton.hxx" -#include -#include #include -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) #include -#else -#include -#endif // IDF v4.1+ +#include +#include namespace openlcb { @@ -220,7 +214,6 @@ public: /// @param fd is the file descriptor used for the configuration settings. void factory_reset(int fd) override; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) /// Processes an event coming from the ESP-IDF default event loop. /// /// @param ctx context parameter (unused). @@ -231,22 +224,6 @@ public: /// NOTE: This is not intended to be called by the user. static void process_idf_event(void *ctx, esp_event_base_t event_base , int32_t event_id, void *event_data); -#else - /// Processes an ESP-IDF WiFi event based on the event raised by the - /// ESP-IDF event loop processor. This should be used when the - /// Esp32WiFiManager is not managing the WiFi or MDNS systems so that - /// it can react to WiFi events to cleanup or recreate the hub or uplink - /// connections as required. When Esp32WiFiManager is managing the WiFi - /// connection this method will be called automatically from the - /// esp_event_loop. Note that ESP-IDF only supports one callback being - /// registered. - /// - /// @param ctx context parameter (unused). - /// @param event is the system_event_t raised by ESP-IDF. - /// - /// NOTE: This is not intended to be called by the user. - static esp_err_t process_wifi_event(void *ctx, system_event_t *event); -#endif /// If called, sets the ESP32 wifi stack to log verbose information to the /// console. @@ -601,13 +578,11 @@ private: /// Constant used to determine if the Hub mode should be enabled. static constexpr uint8_t CONN_MODE_HUB_BIT = BIT(1); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) /// Network interfaces that are managed by Esp32WiFiManager. esp_netif_t *espNetIfaces_[MAX_NETWORK_INTERFACES] { nullptr, nullptr }; -#endif // IDF v4.1+ /// This class provides a proxy for the @ref Notifiable provided by the /// @ref SocketClient. This is necessary to ensure the @ref Notifiable does diff --git a/src/openlcb/ApplicationChecksum.hxx b/src/openlcb/ApplicationChecksum.hxx index 0512a966a..4b6e390bb 100644 --- a/src/openlcb/ApplicationChecksum.hxx +++ b/src/openlcb/ApplicationChecksum.hxx @@ -35,7 +35,7 @@ #ifndef _OPENLCB_APPLICATIONCHECKSUM_HXX_ #define _OPENLCB_APPLICATIONCHECKSUM_HXX_ -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "bootloader_hal.h" #else #include "freertos/bootloader_hal.h" diff --git a/src/openlcb/Bootloader.hxx b/src/openlcb/Bootloader.hxx index 8bc426faa..713e86d05 100644 --- a/src/openlcb/Bootloader.hxx +++ b/src/openlcb/Bootloader.hxx @@ -43,7 +43,7 @@ #include #include -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "bootloader_hal.h" #else #include "freertos/bootloader_hal.h" diff --git a/src/openlcb/BroadcastTime.cxx b/src/openlcb/BroadcastTime.cxx index e74e57103..11444f107 100644 --- a/src/openlcb/BroadcastTime.cxx +++ b/src/openlcb/BroadcastTime.cxx @@ -46,7 +46,7 @@ namespace openlcb // void BroadcastTime::clear_timezone() { -#ifndef ESP32 +#ifndef ESP_PLATFORM setenv("TZ", "GMT0", 1); tzset(); #endif diff --git a/src/openlcb/CompileCdiMain.cxx b/src/openlcb/CompileCdiMain.cxx index 2569b1d87..aef8578a8 100644 --- a/src/openlcb/CompileCdiMain.cxx +++ b/src/openlcb/CompileCdiMain.cxx @@ -27,6 +27,9 @@ void render_cdi_helper(const CdiType &t, string ns, string name) t.config_renderer().render_cdi(&payload); if (raw_render) { + // Adds trailing zero to the file written. + payload.push_back(0); + // Writes the file. string filename = name + ".xmlout"; printf("Writing %d bytes to %s\n", (int)payload.size(), filename.c_str()); diff --git a/src/openlcb/ConfigRenderer.cxxtest b/src/openlcb/ConfigRenderer.cxxtest index 2d3125bca..5f58e3497 100644 --- a/src/openlcb/ConfigRenderer.cxxtest +++ b/src/openlcb/ConfigRenderer.cxxtest @@ -351,5 +351,26 @@ TEST(CdiRender, RenderIdent) EXPECT_EQ(34u, cfg.testseg().e2().offset()); } + +CDI_GROUP(TestCdi3, MainCdi()); +CDI_GROUP_ENTRY(acdi, Acdi); +CDI_GROUP_ENTRY(testseg, OtherSegment, Hidden(true)); +CDI_GROUP_END(); + +TEST(CdiRender, NoRenderHiddenSegment) +{ + string s; + TestCdi3 cfg(0); + cfg.config_renderer().render_cdi(&s); + const char kExpectedTestNodeCdi[] = "" R"data( + + + +)data"; + EXPECT_EQ(kExpectedTestNodeCdi, s); + EXPECT_EQ(34u, cfg.testseg().e2().offset()); +} + + } // namespace } // namespace openlcb diff --git a/src/openlcb/ConfigRenderer.hxx b/src/openlcb/ConfigRenderer.hxx index c32e55783..cc307870f 100644 --- a/src/openlcb/ConfigRenderer.hxx +++ b/src/openlcb/ConfigRenderer.hxx @@ -406,7 +406,11 @@ public: GroupConfigOptions opts(args..., Body::group_opts()); if (opts.hidden()) { - EmptyGroupConfigRenderer(Body::size() * replication_).render_cdi(s); + if (!opts.is_segment()) + { + EmptyGroupConfigRenderer(Body::size() * replication_) + .render_cdi(s); + } return; } const char *tag = nullptr; diff --git a/src/openlcb/ConfigRepresentation.cxxtest b/src/openlcb/ConfigRepresentation.cxxtest index 777ab3890..0e58c9ece 100644 --- a/src/openlcb/ConfigRepresentation.cxxtest +++ b/src/openlcb/ConfigRepresentation.cxxtest @@ -142,7 +142,7 @@ TEST(FullCdi, SegmentOffset) EXPECT_TRUE(def.group_opts().is_cdi()); EXPECT_EQ(-2, def.group_opts().segment()); - EXPECT_EQ(INT_MAX, def.ident().group_opts().offset()); + EXPECT_EQ(0, def.ident().group_opts().offset()); EXPECT_EQ(0u, def.ident().group_opts().get_segment_offset()); EXPECT_EQ(0u, def.ident().offset()); EXPECT_EQ(1000, def.ident().group_opts().segment()); @@ -156,6 +156,7 @@ TEST(FullCdi, SegmentOffset) EXPECT_EQ(128u, def.seg().offset()); EXPECT_EQ(128u, def.seg().first().offset()); + EXPECT_EQ(0, def.seg2().group_opts().offset()); EXPECT_EQ(0u, def.seg2().first().offset()); EXPECT_EQ(142u, def.seg3().first().offset()); diff --git a/src/openlcb/DccAccyConsumer.cxxtest b/src/openlcb/DccAccyConsumer.cxxtest index d961e3878..13bbac1f1 100644 --- a/src/openlcb/DccAccyConsumer.cxxtest +++ b/src/openlcb/DccAccyConsumer.cxxtest @@ -58,6 +58,11 @@ public: class DccAccyTest : public AsyncNodeTest { protected: + DccAccyTest() + { + wait(); + } + StrictMock trackSendQueue_; DccAccyConsumer consumer_{node_, &trackSendQueue_}; }; @@ -72,6 +77,19 @@ TEST_F(DccAccyTest, identify_boot_invalid) ":X198F4111N0101020000FF01F0;", ":X194C722AN0101020000FF01F0;"); } +TEST_F(DccAccyTest, global_identify) +{ + clear_expect(true); + // Consumer range for two 4096 long ranges. + expect_packet(":X194A422AN0101020000FF0FFF;"); + expect_packet(":X194A422AN0101020000FE0FFF;"); + + // identify events addressed. + send_packet(":X19968111N022A;"); + wait(); +} + + TEST_F(DccAccyTest, identify_throw_identify) { EXPECT_CALL(trackSendQueue_, arrived(_, _)).Times(2); @@ -122,4 +140,66 @@ TEST_F(DccAccyTest, packet_throw) wait(); } + +class DccExtAccyTest : public AsyncNodeTest +{ +protected: + DccExtAccyTest() + { + wait(); + } + + StrictMock trackSendQueue_; + DccExtAccyConsumer consumer_{node_, &trackSendQueue_}; +}; + +TEST_F(DccExtAccyTest, create) +{ +} + +TEST_F(DccExtAccyTest, global_identify) +{ + clear_expect(true); + // Consumer range for one very long range (11 bit plus 8 bit). + expect_packet(":X194A422AN010102000107FFFF;"); + + // identify events addressed. + send_packet(":X19968111N022A;"); + wait(); +} + + +TEST_F(DccExtAccyTest, identify_unknown) +{ + // unknown identify + send_packet_and_expect_response( + ":X198F4111N0101020001035a77;", ":X194C722AN0101020001035a77;"); +} + +TEST_F(DccExtAccyTest, packet_throw) +{ + uint8_t hdr = 0b01100100; + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xa5, _))); + + // set to 0xa5 + send_packet(":X195B4111N010102000106c5a5;"); + + + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0x00, _))); + + // set to 0x00 + send_packet(":X195B4111N010102000106c500;"); + + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xff, _))); + + // set to 0xff + send_packet(":X195B4111N010102000106c5ff;"); + + wait(); +} + + } // namespace openlcb diff --git a/src/openlcb/DccAccyConsumer.hxx b/src/openlcb/DccAccyConsumer.hxx index 795fa9e82..f486ea614 100644 --- a/src/openlcb/DccAccyConsumer.hxx +++ b/src/openlcb/DccAccyConsumer.hxx @@ -74,21 +74,31 @@ protected: void handle_identify_global(const EventRegistryEntry ®istry_entry, EventReport *event, BarrierNotifiable *done) OVERRIDE { + AutoNotify an(done); if (event->dst_node && event->dst_node != node_) { - return done->notify(); + return; + } + if (registry_entry.event == + TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE) + { + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, + 4095)), + done->new_child()); + } + if (registry_entry.event == + TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE) + { + event->event_write_helper<2>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, + 4095)), + done->new_child()); } - event->event_write_helper<1>()->WriteAsync(node_, - Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), - eventid_to_buffer(EncodeRange( - TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 2044)), - done->new_child()); - event->event_write_helper<2>()->WriteAsync(node_, - Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), - eventid_to_buffer(EncodeRange( - TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 2044)), - done->new_child()); - done->notify(); } void handle_event_report(const EventRegistryEntry ®istry_entry, @@ -250,6 +260,150 @@ private: dcc::TrackIf *track_; }; +/// Base (generic protocol) implementation of the DCC extended accessory +/// consumer. Unlike the basic accessory version, this one does not remember +/// the last set state. +class DccExtAccyConsumerBase : public SimpleEventHandler +{ +protected: + /// How may addresses are there for extended accessories. + static constexpr unsigned NUM_ADDRESS = 2048; + /// How may aspects are supported per accessory. + static constexpr unsigned NUM_ASPECT = 256; + /// Total number of events we are listening for. + static constexpr unsigned NUM_EVENT = NUM_ASPECT * NUM_ADDRESS; + + /// Constructs a listener for DCC extended accessory control. + /// @param node is the virtual node that will be listening for events and + /// responding to Identify messages. + DccExtAccyConsumerBase(Node *node) + : node_(node) + { + EventRegistry::instance()->register_handler( + EventRegistryEntry( + this, TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE), + 11+8 /*number of bits*/); + } + + /// Destructor. + ~DccExtAccyConsumerBase() + { + EventRegistry::instance()->unregister_handler(this); + } + + void handle_identify_global(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) OVERRIDE + { + AutoNotify an(done); + if (event->dst_node && event->dst_node != node_) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE, NUM_EVENT - 1)), + done->new_child()); + } + + void handle_event_report(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (!parse_event(event->event)) + { + return; + } + send_accy_command(); + } + + void handle_identify_consumer(const EventRegistryEntry &entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (!parse_event(event->event)) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_UNKNOWN, WriteHelper::global(), + eventid_to_buffer(event->event), done->new_child()); + } + + /// Send the actual accessory command. + virtual void send_accy_command() = 0; + + /// Parses an event into an openlcb accessory offset. + /// @return true if the event is in the accessory range, false if this + /// event can be ignored. + /// @param on_off will be set to true if this is an activate event, false + /// if it is an inactivate event. + /// @param ofs will be set to the offset in the state_ arrays. + /// @param mask will be set to a single bit value that marks the location + /// in the state_ arrays. + bool parse_event(EventId event) + { + if (event >= TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE && + event < + TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE + NUM_EVENT) + { + aspect_ = event & 0xff; + dccAddress_ = (event >> 8) & (NUM_ADDRESS - 1); + return true; + } + else + { + return false; + } + } + + + /// Parsed event state: dcc address (0..2047) without inverting or encoding. + unsigned dccAddress_ : 11; + /// Parsed event state: the aspect commanded. + unsigned aspect_ : 8; + + /// OpenLCB node to export the consumer on. + Node *node_; +}; + +/// Specialized (DCC protocol) implementation of a DCC extended accessory +/// consumer. +class DccExtAccyConsumer : public DccExtAccyConsumerBase +{ +public: + /// Constructs a listener for DCC accessory control. + /// @param node is the virtual node that will be listening for events and + /// responding to Identify messages. + /// @param track is the interface through which we will be writing DCC + /// accessory packets. + DccExtAccyConsumer(Node *node, dcc::TrackIf *track) + : DccExtAccyConsumerBase(node) + , track_(track) + { + } + + /// Destructor. + ~DccExtAccyConsumer() + { + } + +private: + /// Send the actual accessory command. + void send_accy_command() override + { + dcc::TrackIf::message_type *pkt; + mainBufferPool->alloc(&pkt); + pkt->data()->add_dcc_ext_accessory(dccAddress_, aspect_); + pkt->data()->packet_header.rept_count = 3; + track_->send(pkt); + } + + /// Track to send DCC packets to. + dcc::TrackIf *track_; +}; + + } // namespace openlcb #endif // _OPENLCB_DCCACCYCONSUMER_HXX_ diff --git a/src/openlcb/LatencyTestConsumer.hxx b/src/openlcb/LatencyTestConsumer.hxx new file mode 100644 index 000000000..727decc46 --- /dev/null +++ b/src/openlcb/LatencyTestConsumer.hxx @@ -0,0 +1,133 @@ +/** \copyright + * Copyright (c) 2024, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file LatencyTestConsumer.hxx + * + * Utility class for determining the response latency of a node. + * + * @author Balazs Racz + * @date 2 Feb 2024 + */ + +#ifndef _OPENLCB_LATENCYTESTCONSUMER_HXX_ +#define _OPENLCB_LATENCYTESTCONSUMER_HXX_ + +#include "openlcb/EventHandlerTemplates.hxx" + +namespace openlcb +{ + +/// This event consumer works together with the hub_test application in order +/// to detect the response latency of a node. The theory of operation is that +/// hub_test sends out an identify consumer message with a given event ID, and +/// this consumer is going to respond. The hub_test then measures the response +/// latency. There is a big event range being advertised (32 bits), and +/// hub_test will send events with different IDs to be able to match request to +/// response. +/// +/// Here in the consumer the only thing we need to do is respond to identify +/// consumer messages with consumer identified. +/// +/// An additional feature is to be able to measure an arbitrary processing step +/// inside the node. For this purpose we have a hook function. When the +/// identify producer message comes in, we call the hook. When the measured +/// process completes, it should notify the given notifiable. Only thereafter +/// the consumer will reply on the bus. Requests' handling is not +/// parallelized. If the hook process cannot complete the requests fast enough, +/// the node will run out of memory and crash. +class LatencyTestConsumer : public SimpleEventHandler +{ +public: + /// To complete the hook, call the notifiable. + using HookFn = std::function; + + LatencyTestConsumer(Node *node, HookFn hook = nullptr) + : node_(node) + , hook_(hook) + { + EventRegistry::instance()->register_handler( + EventRegistryEntry(this, EVENT_BASE), 32); + } + + void handle_identify_global(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (event->dst_node && event->dst_node != node_) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange(EVENT_BASE, 0xffffffff)), + done->new_child()); + } + + void handle_identify_consumer(const EventRegistryEntry &entry, + EventReport *event, BarrierNotifiable *done) override + { + event_ = event; + done_ = done; + if (hook_) + { + hook_(new TempNotifiable([this]() { reply(); })); + } + else + { + reply(); + } + } + +private: + void reply() + { + AutoNotify an(done_); + event_->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_UNKNOWN, WriteHelper::global(), + eventid_to_buffer(event_->event), done_->new_child()); + } + + /// This is within NMRA ID 1, which is not assigned. Lower four bytes are + /// the id. + static constexpr EventId EVENT_BASE = 0x0900013900000000; + + /// Which node should be sending responses. + Node *node_; + + /// The owner's hook will be invoked and run (asynchronous) before + /// replying.. + HookFn hook_; + + /// Incoming event report we are working on. + EventReport *event_; + + /// Will notify this after sending the reply. + BarrierNotifiable *done_ {nullptr}; +}; + +} // namespace openlcb + +#endif // _OPENLCB_LATENCYTESTCONSUMER_HXX_ diff --git a/src/openlcb/MemoryConfig.hxx b/src/openlcb/MemoryConfig.hxx index bab03b5a4..9287b50bc 100644 --- a/src/openlcb/MemoryConfig.hxx +++ b/src/openlcb/MemoryConfig.hxx @@ -670,8 +670,8 @@ private: // Custom spaces cannot do free yet. return respond_reject(Defs::ERROR_INVALID_ARGS); } - // Fall through. } + // Fall through case MemoryConfigDefs::COMMAND_ENTER_BOOTLOADER: { enter_bootloader(); @@ -733,7 +733,8 @@ private: return exit(); } LOG(VERBOSE, "memcfg handler reply: no client registered"); - } // fall through to unsupported. + // fall through to unsupported + } // fall through default: // Unknown/unsupported command, reject datagram. return respond_reject(Defs::ERROR_UNIMPLEMENTED_SUBCMD); diff --git a/src/openlcb/ServoConsumer.hxx b/src/openlcb/ServoConsumer.hxx index 9783244c6..29771cbb9 100644 --- a/src/openlcb/ServoConsumer.hxx +++ b/src/openlcb/ServoConsumer.hxx @@ -1,7 +1,7 @@ #ifndef _OPENLCB_SERVOCONSUMER_HXX_ #define _OPENLCB_SERVOCONSUMER_HXX_ -#if defined(ARDUINO) || defined(ESP32) +#if defined(ARDUINO) || defined(ESP_PLATFORM) #include "freertos_drivers/arduino/DummyGPIO.hxx" #include "freertos_drivers/arduino/PWM.hxx" #else diff --git a/src/openlcb/SimpleInfoProtocol.hxx b/src/openlcb/SimpleInfoProtocol.hxx index b8def4bb2..f6ed993df 100644 --- a/src/openlcb/SimpleInfoProtocol.hxx +++ b/src/openlcb/SimpleInfoProtocol.hxx @@ -221,8 +221,8 @@ private: #if OPENMRN_HAVE_POSIX_FD case SimpleInfoDescriptor::FILE_CHAR_ARRAY: open_and_seek_next_file(); - // fall through #endif + // Fall through case SimpleInfoDescriptor::CHAR_ARRAY: byteOffset_ = 0; currentLength_ = d.arg; diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index 2ab6dbdb6..4927b0d4b 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -73,6 +73,8 @@ struct TractionDefs { static constexpr uint64_t ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE = 0x0101020000FF0000ULL; /// Base address of DCC accessory decoder well-known event range (inactive) static constexpr uint64_t INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE = 0x0101020000FE0000ULL; + /// Base address of DCC extended accessory decoder well-known event range + static constexpr uint64_t EXT_DCC_ACCESSORY_EVENT_BASE = 0x0101020001000000ULL; /// Node ID space allocated for DC blocks. static const uint64_t NODE_ID_DC_BLOCK = 0x060000000000ULL; /// Node ID space allocated for DCC locomotives. diff --git a/src/os/OS.hxx b/src/os/OS.hxx index 50edeb6cd..bbf752c8d 100644 --- a/src/os/OS.hxx +++ b/src/os/OS.hxx @@ -733,7 +733,7 @@ private: /** handle to event object */ EventGroupHandle_t event; }; -#elif defined(ARDUINO) && !defined(ESP32) +#elif defined(ARDUINO) && !defined(ESP_PLATFORM) typedef uint32_t OSEventType; diff --git a/src/os/OSSelectWakeup.cxx b/src/os/OSSelectWakeup.cxx index 6d1fef562..d3ed3784f 100644 --- a/src/os/OSSelectWakeup.cxx +++ b/src/os/OSSelectWakeup.cxx @@ -55,6 +55,11 @@ int OSSelectWakeup::select(int nfds, fd_set *readfds, else { #if OPENMRN_FEATURE_DEVICE_SELECT + // It is important that we do this select_clear() in the same + // critical section as checking pendingWakeup above. This ensures + // tht all wakeups before this clear cause sleep to be zero, and + // all wakeups after this clear will cause the event group bit to + // be set. Device::select_clear(); #endif } @@ -74,7 +79,7 @@ int OSSelectWakeup::select(int nfds, fd_set *readfds, int ret = ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); #elif OPENMRN_HAVE_SELECT -#ifdef ESP32 +#ifdef ESP_PLATFORM fd_set newexcept; if (!exceptfds) { @@ -86,7 +91,7 @@ int OSSelectWakeup::select(int nfds, fd_set *readfds, { nfds = vfsFd_ + 1; } -#endif //ESP32 +#endif // ESP_PLATFORM struct timeval timeout; // divide in two steps to avoid overflow on ESP32 timeout.tv_sec = (deadline_nsec / 1000) / 1000000LL; @@ -107,12 +112,13 @@ int OSSelectWakeup::select(int nfds, fd_set *readfds, return ret; } -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "freertos_includes.h" #include #include #include +#include #include #include @@ -129,16 +135,7 @@ static pthread_key_t select_wakeup_key; /// all VFS APIs we implement. static constexpr int WAKEUP_VFS_FD = 0; -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) -extern "C" -{ - void *sys_thread_sem_get(); - void sys_sem_signal(void *); - void sys_sem_signal_isr(void *); -} -#endif // NOT IDF v4.0+ -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) /// This function is called by the ESP32's select implementation. It is passed /// in as a function pointer to the VFS API. /// @param nfds see standard select API @@ -150,31 +147,12 @@ extern "C" /// @param end_select_args are the arguments to pass to end_select upon wakeup. static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, esp_vfs_select_sem_t signal_sem, void **end_select_args) -#else // NOT IDF v4.0+ -/// This function is called by the ESP32's select implementation. It is passed -/// in as a function pointer to the VFS API. -/// @param nfds see standard select API -/// @param readfds see standard select API -/// @param writefds see standard select API -/// @param exceptfds see standard select API -/// @param signal_sem if non-NULL, the select can be woken up by notifying this -/// semaphore. If NULL, the select can be woken up by notifying the LWIP -/// semaphore. By the API contract this pointer needs to be passed into -/// esp_vfs_select_triggered. -static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, - fd_set *exceptfds, esp_vfs_select_sem_t signal_sem) -#endif // IDF v4.0+ { OSSelectWakeup *parent = (OSSelectWakeup *)pthread_getspecific(select_wakeup_key); HASSERT(parent); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) LOG(VERBOSE, "esp start select %p (thr %p parent %p)", signal_sem.sem, os_thread_self(), parent); -#else // NOT IDF v4.0+ - LOG(VERBOSE, "esp start select %p (thr %p parent %p)", signal_sem, - os_thread_self(), parent); -#endif // IDF v4.0+ // Check if our VFS FD is included in exceptfds before tracking that we // should possibly wake up early. @@ -185,7 +163,6 @@ static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, return ESP_OK; } -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) /// This function is called inline from the ESP32's select implementation. It is /// passed in as a function pointer to the VFS API. /// @@ -193,11 +170,6 @@ static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, /// is not used today. /// @return always returns ESP_OK as this is a no-op. static esp_err_t esp_end_select(void *arg) -#else // NOT IDF v4.0+ -/// This function is called inline from the ESP32's select implementation. It is -/// passed in as a function pointer to the VFS API. -static void esp_end_select() -#endif // IDF v4.0+ { OSSelectWakeup *parent = (OSSelectWakeup *)pthread_getspecific(select_wakeup_key); @@ -205,9 +177,7 @@ static void esp_end_select() LOG(VERBOSE, "esp end select (thr %p parent %p)", os_thread_self(), parent); parent->esp_end_select(); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) return ESP_OK; -#endif // IDF v4.0+ } /// This function is called by the ESP32's select implementation. @@ -224,6 +194,15 @@ void OSSelectWakeup::esp_start_select(fd_set *readfds, fd_set *writefds, exceptFds_ = exceptfds; exceptFdsOrig_ = *exceptfds; FD_ZERO(exceptFds_); + if (pendingWakeup_) + { + // There is a race condition between the Executor deciding to run + // select, and the internal implementation of select() calling this + // function. Since we only get the semaphone in this call, the wakeup + // functions are noops if they hit during this window. If there was a + // missed wakeup, we repeat it. + esp_wakeup(); + } } /// This function marks the stored semaphore as invalid which indicates no @@ -253,35 +232,14 @@ void OSSelectWakeup::esp_wakeup() // and not the system global FD. FD_SET(WAKEUP_VFS_FD, exceptFds_); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) LOG(VERBOSE, "wakeup es %p %u", espSem_.sem, *(unsigned*)espSem_.sem); esp_vfs_select_triggered(espSem_); -#else // IDF v3.x - LOG(VERBOSE, "wakeup es %p %u", espSem_, *(unsigned*)espSem_); - if (espSem_) - { - // Mark the VFS implementation FD for the wakeup call. Note that this - // should not use vfsFd_ since the fd_set will contain the VFS specific - // FD and not the system global FD. - esp_vfs_select_triggered(espSem_); - } - else - { - // Works around a bug in the implementation of - // esp_vfs_select_triggered, which internally calls - // sys_sem_signal(sys_thread_sem_get()); This is buggy because - // sys_thread_sem_get() will get the semaphore that belongs to the - // calling thread, not the target thread to wake up. - sys_sem_signal(lwipSem_); - } -#endif // IDF >= v4.0 } /// This function will trigger the ESP32 to wake up from any pending select() /// call from within an ISR context. void OSSelectWakeup::esp_wakeup_from_isr() { - AtomicHolder h(this); BaseType_t woken = pdFALSE; // If our VFS FD is not set in the except fd_set we can exit early. @@ -295,23 +253,7 @@ void OSSelectWakeup::esp_wakeup_from_isr() // and not the system global FD. FD_SET(WAKEUP_VFS_FD, exceptFds_); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) esp_vfs_select_triggered_isr(espSem_, &woken); -#else // IDF v3.x - if (espSem_) - { - esp_vfs_select_triggered_isr(espSem_, &woken); - } - else - { - // Works around a bug in the implementation of - // esp_vfs_select_triggered, which internally calls - // sys_sem_signal(sys_thread_sem_get()); This is buggy because - // sys_thread_sem_get() will get the semaphore that belongs to the - // calling thread, not the target thread to wake up. - sys_sem_signal_isr(lwipSem_); - } -#endif // IDF >= v4.0 if (woken == pdTRUE) { @@ -339,10 +281,6 @@ static void esp_vfs_init() void OSSelectWakeup::esp_allocate_vfs_fd() { -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) - lwipSem_ = sys_thread_sem_get(); -#endif // IDF < v4.0 - HASSERT(0 == pthread_once(&vfs_init_once, &esp_vfs_init)); vfsFd_ = ::open("/dev/wakeup/0", 0, 0); HASSERT(vfsFd_ >= 0); @@ -360,4 +298,4 @@ void OSSelectWakeup::esp_deallocate_vfs_fd() vfsFd_ = -1; } -#endif // ESP32 +#endif // ESP_PLATFORM diff --git a/src/os/OSSelectWakeup.hxx b/src/os/OSSelectWakeup.hxx index a6c8aca87..ed45a6adf 100644 --- a/src/os/OSSelectWakeup.hxx +++ b/src/os/OSSelectWakeup.hxx @@ -54,7 +54,7 @@ #include #endif -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "sdkconfig.h" #ifdef CONFIG_VFS_SUPPORT_TERMIOS @@ -65,16 +65,8 @@ #endif // CONFIG_VFS_SUPPORT_TERMIOS #include -#include -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) -// SemaphoreHandle_t is defined by inclusion of esp_vfs.h so no additional -// includes are necessary. -/// Alias for the internal data type used by ESP-IDF select() calls. -typedef SemaphoreHandle_t * esp_vfs_select_sem_t; -#endif // IDF v3.x - -#endif // ESP32 +#endif // ESP_PLATFORM /// Signal handler that does nothing. @param sig ignored. void empty_signal_handler(int sig); @@ -91,7 +83,7 @@ public: ~OSSelectWakeup() { -#ifdef ESP32 +#ifdef ESP_PLATFORM esp_deallocate_vfs_fd(); #endif } @@ -108,7 +100,7 @@ public: { // Gets the current thread. thread_ = os_thread_self(); -#ifdef ESP32 +#ifdef ESP_PLATFORM esp_allocate_vfs_fd(); #endif #if OPENMRN_FEATURE_DEVICE_SELECT @@ -149,7 +141,7 @@ public: Device::select_wakeup(©); #elif OPENMRN_HAVE_PSELECT pthread_kill(thread_, WAKEUP_SIG); -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) esp_wakeup(); #elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) DIE("need wakeup code"); @@ -167,6 +159,10 @@ public: #if OPENMRN_FEATURE_RTOS_FROM_ISR void wakeup_from_isr() { +#if defined(ESP_PLATFORM) + // On multi-core ESP32s we need to lock objects even in ISRs. + AtomicHolder h(this); +#endif pendingWakeup_ = true; if (inSelect_) { @@ -179,7 +175,7 @@ public: // TODO: confirm if pthread_kill is ISR safe //#elif OPENMRN_HAVE_PSELECT // pthread_kill(thread_, WAKEUP_SIG); -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) esp_wakeup_from_isr(); #else DIE("need wakeup code"); @@ -206,7 +202,7 @@ public: long long deadline_nsec); private: -#ifdef ESP32 +#ifdef ESP_PLATFORM void esp_allocate_vfs_fd(); void esp_deallocate_vfs_fd(); void esp_wakeup(); @@ -233,12 +229,7 @@ private: /// when waking up early from select(). fd_set exceptFdsOrig_; -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) - /// Semaphore for waking up LwIP select. - void* lwipSem_{nullptr}; -#endif // IDF < v4.0 - -#endif // ESP32 +#endif // ESP_PLATFORM #if OPENMRN_HAVE_PSELECT /** This signal is used for the wakeup kill in a pthreads OS. */ diff --git a/src/os/os.c b/src/os/os.c index d51c51f60..fa67a7e9f 100644 --- a/src/os/os.c +++ b/src/os/os.c @@ -925,7 +925,7 @@ int ignore_fn(void) return 0; } -#if !defined(ARDUINO) && !defined(ESP32) +#if !defined(ARDUINO) && !defined(ESP_PLATFORM) #if !defined (__MINGW32__) int main(int argc, char *argv[]) __attribute__ ((weak)); @@ -984,7 +984,7 @@ int main(int argc, char *argv[]) #endif } -#endif // ESP32 +#endif // ESP_PLATFORM #if defined(ARDUINO) unsigned critical_nesting; diff --git a/src/utils/Atomic.hxx b/src/utils/Atomic.hxx index 05d0f71aa..fe9b5b41d 100644 --- a/src/utils/Atomic.hxx +++ b/src/utils/Atomic.hxx @@ -71,7 +71,7 @@ public: } }; -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) #include "freertos_includes.h" diff --git a/src/utils/HubDevice.hxx b/src/utils/HubDevice.hxx index 345fe6b79..2bd712759 100644 --- a/src/utils/HubDevice.hxx +++ b/src/utils/HubDevice.hxx @@ -48,7 +48,8 @@ template class FdHubWriteFlow; class FdHubPortBase : public FdHubPortInterface, private Atomic { public: -#ifdef ESP32 // TODO: shrink these if possible +#ifdef ESP_PLATFORM + // TODO: shrink these if possible /// How many bytes of stack should we allocate to the write thread's stack. static const int kWriteThreadStackSize = 2048; /// How many bytes of stack should we allocate to the read thread's stack. @@ -58,7 +59,7 @@ public: static const int kWriteThreadStackSize = 1000; /// How many bytes of stack should we allocate to the read thread's stack. static const int kReadThreadStackSize = 1000; -#endif // ESP32 +#endif // ESP_PLATFORM /// Constructor. /// @param fd is the filedes to read/write diff --git a/src/utils/SocketClient.cxx b/src/utils/SocketClient.cxx index cb349346b..706f858cf 100644 --- a/src/utils/SocketClient.cxx +++ b/src/utils/SocketClient.cxx @@ -42,10 +42,11 @@ #include #include -#ifndef ESP32 // these don't exist on the ESP32 with LWiP +#ifndef ESP_PLATFORM +// these don't exist on the ESP32 with LWiP #include #include -#endif // ESP32 +#endif // ESP_PLATFORM #include #include #include @@ -57,9 +58,10 @@ #include "utils/macros.h" #include "utils/logging.h" -#ifdef ESP32 // this is not declared in netdb.h on ESP32 +#ifdef ESP_PLATFORM +// this is not declared in netdb.h on ESP32 const char *gai_strerror (int __ecode); -#endif // ESP32 +#endif // ESP_PLATFORM int ConnectSocket(const char *host, int port) { diff --git a/src/utils/SocketClient.hxx b/src/utils/SocketClient.hxx index f3c7d494a..3b46b5b31 100644 --- a/src/utils/SocketClient.hxx +++ b/src/utils/SocketClient.hxx @@ -37,7 +37,8 @@ #include #include -#ifndef ESP32 // this doesn't exist on the ESP32 with LWiP +#ifndef ESP_PLATFORM +// this doesn't exist on the ESP32 with LWiP #include #endif #include diff --git a/src/utils/Stats.cxx b/src/utils/Stats.cxx new file mode 100644 index 000000000..b765c3129 --- /dev/null +++ b/src/utils/Stats.cxx @@ -0,0 +1,43 @@ +/** \copyright + * Copyright (c) 2023, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Stats.hxx + * + * Utility class for collecting statistics. + * + * @author Balazs Racz + * @date 28 Dec 2023 + */ + +#include "utils/Stats.hxx" + +#include "utils/StringPrintf.hxx" + +std::string Stats::debug_string() +{ + return StringPrintf("%.1f msec +- %.1f, max %.1f\n", favg() / 1000, + stddev() / 1000, ((FloatType)max_) / 1000); +} diff --git a/src/utils/Stats.hxx b/src/utils/Stats.hxx new file mode 100644 index 000000000..c3ad10058 --- /dev/null +++ b/src/utils/Stats.hxx @@ -0,0 +1,111 @@ +/** \copyright + * Copyright (c) 2023, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Stats.hxx + * + * Utility class for collecting statistics. + * + * @author Balazs Racz + * @date 28 Dec 2023 + */ + +#ifndef _UTILS_STATS_HXX_ +#define _UTILS_STATS_HXX_ + +#include +#include +#include +#include + +class Stats +{ +public: + using FloatType = double; + using ValueType = int32_t; + + Stats() + { + clear(); + } + + // Clear the statistics (erase all data points). + void clear() + { + sum_ = 0; + count_ = 0; + qsum_ = 0; + max_ = std::numeric_limits::min(); + } + + /// Appends a data point to the statistics. + /// @param value the data point. + void add(ValueType value) + { + ++count_; + sum_ += value; + FloatType fval = value; + qsum_ += fval * fval; + if (value > max_) + { + max_ = value; + } + } + + /// @return the average + FloatType favg() + { + return FloatType(sum_) / count_; + } + + /// @return the average (rounded down to nearest integer). + int32_t avg() + { + return sum_ / count_; + } + + /// @return the sample standard deviation (uncorrected). + FloatType stddev() + { + // Formula: sqrt(N*qsum - sum^2) / N + FloatType sum(sum_); + return sqrt(qsum_ * count_ - sum * sum) / count_; + } + + /// Creates a half-a-line printout of this stats object for debug purposes. + std::string debug_string(); + +private: + /// Number of samples added. + uint32_t count_; + /// Maximum value found since the last clear. + ValueType max_; + /// Sum of sample values added. + int64_t sum_; + /// Sum of squares of sample values added. + FloatType qsum_; +}; + +#endif // _UTILS_STATS_HXX_ diff --git a/src/utils/constants.cxx b/src/utils/constants.cxx index 1d70bbd49..a6553a47c 100644 --- a/src/utils/constants.cxx +++ b/src/utils/constants.cxx @@ -159,7 +159,7 @@ DEFAULT_CONST(gridconnect_tcp_notsent_lowat_buffer_size, 1); DEFAULT_CONST_FALSE(gridconnect_tcp_use_select); -#ifdef ESP32 +#ifdef ESP_PLATFORM /// Use a stack size of 3kb for SocketListener tasks. DEFAULT_CONST(socket_listener_stack_size, 3072); /// Allow one socket to be pending for accept() in SocketListener. @@ -169,4 +169,4 @@ DEFAULT_CONST(socket_listener_backlog, 1); DEFAULT_CONST(socket_listener_stack_size, 1000); /// Allow up to five sockets to be pending for accept() in SocketListener. DEFAULT_CONST(socket_listener_backlog, 5); -#endif +#endif // ESP_PLATFORM diff --git a/src/utils/logging.cxx b/src/utils/logging.cxx index 9cc01b3eb..ca433da3b 100644 --- a/src/utils/logging.cxx +++ b/src/utils/logging.cxx @@ -35,7 +35,7 @@ #if defined(__linux__) || defined(__MACH__) char logbuffer[4096]; -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) char logbuffer[1024]; #else /// Temporary buffer to sprintf() the log lines into. @@ -56,7 +56,8 @@ void log_output(char* buf, int size) { send_stdio_serial_message(buf); } -#elif defined(__linux__) || defined(__MACH__) || defined(__EMSCRIPTEN__) || defined(ESP32) +#elif defined(__linux__) || defined(__MACH__) || defined(__EMSCRIPTEN__) || \ + defined(ESP_PLATFORM) #include "utils/stdio_logging.h" diff --git a/src/utils/logging.h b/src/utils/logging.h index ddee34ecb..0e9bf62b7 100644 --- a/src/utils/logging.h +++ b/src/utils/logging.h @@ -125,7 +125,7 @@ extern os_mutex_t g_log_mutex; #if defined(__linux__) || defined(__MACH__) extern char logbuffer[4096]; -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) extern char logbuffer[1024]; #else /// Temporary buffer to sprintf() the log lines into. diff --git a/src/utils/macros.h b/src/utils/macros.h index 58a48e01c..f06cbf763 100644 --- a/src/utils/macros.h +++ b/src/utils/macros.h @@ -91,12 +91,6 @@ extern const char* g_death_file; #elif defined(ESP32) -// For the ESP32 family or MCUs use ets_printf() instead of printf() to bypass -// internal locking within the newlib implementation which has been observed -// crashing the MCU at times when printf() is used in conjuction with ISRs on -// the same core. The call to assert() internally uses ets_printf() as well -// prior to "hanging" the MCU core in a busy loop. - #include #include @@ -113,6 +107,11 @@ extern const char* g_death_file; #error Unknown/Unsupported ESP32 variant. #endif // CONFIG_IDF_TARGET_ESP32 +// For the ESP32 we are using ets_printf() instead of printf() to avoid the +// internal locking within the newlib implementation. This locking can cause +// difficult to parse backtraces when an ISR is on the same core as the code +// that crashes due to these two macros. + #define HASSERT(x) do { if (!(x)) { ets_printf("Assertion failed in file " __FILE__ " line %d: assert(%s)\n", __LINE__, #x); g_death_file = __FILE__; g_death_lineno = __LINE__; assert(0); abort();} } while(0) #define DIE(MSG) do { ets_printf("Crashed in file " __FILE__ " line %d: " MSG "\n", __LINE__); assert(0); abort(); } while(0) diff --git a/src/utils/socket_listener.cxx b/src/utils/socket_listener.cxx index 5dfa241f4..98fd5341a 100644 --- a/src/utils/socket_listener.cxx +++ b/src/utils/socket_listener.cxx @@ -46,11 +46,12 @@ #include "utils/logging.h" #include "utils/macros.h" -#ifndef ESP32 // these don't exist on the ESP32 with LWiP +#ifndef ESP_PLATFORM +// these don't exist on the ESP32 with LWiP #include #include #include -#endif // ESP32 +#endif // ESP_PLATFORM #include #include #include diff --git a/src/utils/sources b/src/utils/sources index e9fed0849..f24fdbf50 100644 --- a/src/utils/sources +++ b/src/utils/sources @@ -25,6 +25,7 @@ CXXSRCS += \ Queue.cxx \ ReflashBootloader.cxx \ ServiceLocator.cxx \ + Stats.cxx \ SocketCan.cxx \ SocketClient.cxx \ constants.cxx \ diff --git a/src/utils/stdio_logging.h b/src/utils/stdio_logging.h index 22db9acd2..1c29e1759 100644 --- a/src/utils/stdio_logging.h +++ b/src/utils/stdio_logging.h @@ -7,7 +7,7 @@ extern "C" { #endif #if defined(__linux__) || defined(__MACH__) || defined(__EMSCRIPTEN__) || \ - defined(ESP32) + defined(ESP_PLATFORM) #define LOGWEAK __attribute__((weak)) #else #define LOGWEAK