Skip to content

Commit

Permalink
Merge pull request #40 from guido4096/support-complete-arbitration
Browse files Browse the repository at this point in the history
Support the complete ebus arbitration protocol
  • Loading branch information
danielkucera authored Aug 16, 2023
2 parents bb08c93 + 6d29ef5 commit 5f9b72a
Show file tree
Hide file tree
Showing 12 changed files with 899 additions and 110 deletions.
18 changes: 16 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@
"*.tcc": "cpp",
"fstream": "cpp",
"istream": "cpp",
"ostream": "cpp"
}
"ostream": "cpp",
"array": "cpp",
"string": "cpp",
"string_view": "cpp",
"ranges": "cpp",
"functional": "cpp",
"cstddef": "cpp",
"chrono": "cpp"
},
"cSpell.words": [
"ARBITRATIN",
"busstate",
"currentstate",
"newstate",
"synerror"
]
}
54 changes: 54 additions & 0 deletions include/arbitration.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef _ARBITRATION_H_
#define _ARBITRATION_H_

#include "busstate.hpp"

// Implements the arbitration algorithm. Uses the state of the bus to decide what to do.
// Typical usage:
// - try to start the arbitration with "start" method
// - pass each received value on the bus to the "data" method
// which will then tell you what the state of the arbitration is
class Arbitration
{
public:
enum state {none, // no arbitration ongoing
arbitrating, // arbitration ongoing
won1, // won
won2, // won
lost1, // lost
lost2, // lost
error, // error
restart1, // restart the arbitration
restart2, // restart the arbitration
};

Arbitration()
: _arbitrating(false)
, _participateSecond(false)
, _arbitrationAddress(0)
, _restartCount(0)
{}
// Try to start arbitration for the specified master.
// Return values:
// - started : arbitration started. Make sure to pass all bus data to this object through the "data" method
// - not_started : arbitration not started. Possible reasons:
// + the bus is not in a state that allows to start arbitration
// + another arbitration is already ongoing
// + the master address is SYN
// - late : arbitration not started because the start is too late compared to the SYN symbol received
enum result {started, not_started, late};
result start(BusState& busstate, uint8_t master, unsigned long startBitTime);

// A symbol was received on the bus, what does this do to the arbitration state?
// Return values:
// - see description of state enum value
Arbitration::state data (BusState& busstate, uint8_t symbol, unsigned long startBitTime);

private:
bool _arbitrating;
bool _participateSecond;
uint8_t _arbitrationAddress;
int _restartCount;
};

#endif
80 changes: 80 additions & 0 deletions include/bus.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#ifndef _BUS_H_
#define _BUS_H_
#include "main.hpp"
#include "busstate.hpp"
#include "arbitration.hpp"
#include "queue"

#ifdef ESP32
#include "atomic"
#define ATOMIC_INT std::atomic<int>
#else
#define ATOMIC_INT int
#endif
// This object retrieves data from the Serial object and let's
// it flow through the arbitration process. The "read" method
// will return data with meta information that tells what should
// be done with the returned data. This object hides if the
// underlying implementation is synchronous or asynchronous
class BusType
{
public:
// "receive" data should go to all clients that are not in arbitration mode
// "enhanced" data should go only to the arbitrating client
// a client is in arbitration mode if _client is not null
struct data {
bool _enhanced; // is this an enhanced command?
uint8_t _c; // command byte, only used when in "enhanced" mode
uint8_t _d; // data byte for both regular and enhanced command
WiFiClient* _client; // the client that is being arbitrated
WiFiClient* _logtoclient; // the client that needs to log
};
BusType();
~BusType();

// begin and end, like with Serial
void begin();
void end();

// Is there a value available that should be send to a client?
bool read(data& d);
size_t write(uint8_t symbol);
int availableForWrite();
int available();

// std::atomic seems not well supported on esp12e, besides it is also not needed there
ATOMIC_INT _nbrRestarts1;
ATOMIC_INT _nbrRestarts2;
ATOMIC_INT _nbrArbitrations;
ATOMIC_INT _nbrLost1;
ATOMIC_INT _nbrLost2;
ATOMIC_INT _nbrWon1;
ATOMIC_INT _nbrWon2;
ATOMIC_INT _nbrErrors;
ATOMIC_INT _nbrLate;
private:
inline void push (const data& d);
void receive (uint8_t symbol, unsigned long startBitTime);
BusState _busState;
Arbitration _arbitration;
WiFiClient* _client;

#if USE_ASYNCHRONOUS
// handler to be notified when there is signal change on the serial input
static void IRAM_ATTR receiveHandler();

// queue from Bus to read method
QueueHandle_t _queue;

// task to read bytes form the serial object and process them with receive methods
TaskHandle_t _serialEventTask;

static void readDataFromSoftwareSerial(void *args);
#else
std::queue<data> _queue;
#endif
};

extern BusType Bus;

#endif
127 changes: 127 additions & 0 deletions include/busstate.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#ifndef _BUSSTATE_H_
#define _BUSSTATE_H_
#include "main.hpp"
#include "enhanced.hpp"


// Implements the state of the bus. The arbitration process can
// only start at well defined states of the bus. To asses the
// state, all data received on the bus needs to be send to this
// object. The object takes care of startup of the bus and
// recovery when an unexpected event happens.
class BusState {
public:
enum eState {
eStartup, // In startup mode to analyze bus state
eStartupFirstSyn, // Either the bus is busy, it is arbitrating, or it is free to start an arbitration
eStartupSymbolAfterFirstSyn,
eStartupSecondSyn,
eReceivedFirstSYN, // Received SYN
eReceivedAddressAfterFirstSYN, // Received SYN ADDRESS
eReceivedSecondSYN, // Received SYN ADDRESS SYN
eReceivedAddressAfterSecondSYN,// Received SYN ADDRESS SYN ADDRESS
eBusy // Bus is busy; _master is master that won, _byte is first symbol after the master address
};
static const char* enumvalue(eState e)
{
const char* values[] = {
"eStartup",
"eStartupFirstSyn",
"eStartupSymbolAfterFirstSyn",
"eStartupSecondSyn",
"eReceivedFirstSYN",
"eReceivedAddressAfterFirstSYN",
"eReceivedSecondSYN",
"eReceivedAddressAfterSecondSYN",
"eBusy"
};
return values[e];
}
BusState()
: _state(eStartup)
, _previousState(eStartup)
{}
// Evaluate a symbol received on UART and determine what the new state of the bus is
inline void data(uint8_t symbol)
{
switch (_state)
{
case eStartup:
_previousState = _state;
_state = symbol == SYN ? syn(eStartupFirstSyn) : eStartup;
break;
case eStartupFirstSyn:
_previousState = _state;
_state = symbol == SYN ? syn(eReceivedFirstSYN) : eStartupSymbolAfterFirstSyn;
break;
case eStartupSymbolAfterFirstSyn:
_previousState = _state;
_state = symbol == SYN ? syn(eStartupSecondSyn) : eBusy;
break;
case eStartupSecondSyn:
_previousState = _state;
_state = symbol == SYN ? syn(eReceivedFirstSYN) : eBusy;
break;
case eReceivedFirstSYN:
_previousState = _state;
_state = symbol == SYN ? syn(eReceivedFirstSYN) : eReceivedAddressAfterFirstSYN;
_master = symbol;
break;
case eReceivedAddressAfterFirstSYN:
_previousState = _state;
_state = symbol == SYN ? syn(eReceivedSecondSYN ): eBusy;
_symbol = symbol;
break;
case eReceivedSecondSYN:
_previousState = _state;
_state = symbol == SYN ? error(_state, eReceivedFirstSYN) : eReceivedAddressAfterSecondSYN;
_master = symbol;
break;
case eReceivedAddressAfterSecondSYN:
_previousState = _state;
_state = symbol == SYN ? error(_state, eReceivedFirstSYN) : eBusy;
_symbol = symbol;
break;
case eBusy:
_previousState = _state;
_state = symbol == SYN ? syn(eReceivedFirstSYN) : eBusy;
break;
}
}
inline eState syn(eState newstate)
{
_previousSYNtime = _SYNtime;
_SYNtime = micros();
return newstate;
}
eState error(eState currentstate, eState newstate)
{
_previousSYNtime = _SYNtime;
_SYNtime = micros();
DEBUG_LOG ("unexpected SYN on bus while state is %s, setting state to %s m=0x%02x, b=0x%02x %lu us\n", enumvalue(currentstate), enumvalue(newstate), _master, _symbol, microsSincePreviousSyn());
return newstate;
}

void reset()
{
_state = eStartup;
}

unsigned long microsSinceLastSyn()
{
return micros() - _SYNtime;
}

unsigned long microsSincePreviousSyn()
{
return micros() - _previousSYNtime;
}

eState _state;
eState _previousState;
uint8_t _master;
uint8_t _symbol;
unsigned long _SYNtime;
unsigned long _previousSYNtime;
};
#endif
37 changes: 37 additions & 0 deletions include/enhanced.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef _ENHANCED_H_
#define _ENHANCED_H_
#include <WiFiClient.h>

enum symbols {
SYN = 0xAA
};

enum requests {
CMD_INIT = 0,
CMD_SEND,
CMD_START,
CMD_INFO
};

enum responses {
RESETTED = 0x0,
RECEIVED = 0x1,
STARTED = 0x2,
INFO = 0x3,
FAILED = 0xa,
ERROR_EBUS = 0xb,
ERROR_HOST = 0xc
};

enum errors {
ERR_FRAMING = 0x00,
ERR_OVERRUN = 0x01
};

void enhArbitrationDone();
WiFiClient* enhArbitrationRequested(uint8_t& arbitration_client);

int pushEnhClient(WiFiClient* client, uint8_t c, uint8_t d, bool log);
void handleEnhClient(WiFiClient* client);

#endif
27 changes: 21 additions & 6 deletions include/main.hpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
#ifndef _MAIN_HPP_
#define _MAIN_HPP_

#include <WiFiClient.h>
#include <WiFiServer.h>

#define MAX_SRV_CLIENTS 4
#define RXBUFFERSIZE 1024
#define RXBUFFERSIZE 512 // On ESP8266, maximum 512 icw SoftwareSerial, otherwise you run out of heap
#define QUEUE_SIZE 480
#define STACK_PROTECTOR 512 // bytes
#define HOSTNAME "esp-eBus"
#define RESET_MS 1000

#ifdef ESP32
// https://esp32.com/viewtopic.php?t=19788
#define AVAILABLE_THRESHOLD 0
#define UART_TX 20
#define UART_RX 21
#define USE_SOFTWARE_SERIAL 1
#define USE_ASYNCHRONOUS 1 // requires USE_SOFTWARE_SERIAL
#define AVAILABLE_THRESHOLD 0 // https://esp32.com/viewtopic.php?t=19788
#else
#define UART_TX 1
#define UART_RX 3
#define USE_SOFTWARE_SERIAL 0
#define USE_ASYNCHRONOUS 0 // requires USE_SOFTWARE_SERIAL
#define AVAILABLE_THRESHOLD 1
#endif

inline int DEBUG_LOG(const char *format, ...) { return 0;}
int DEBUG_LOG_IMPL(const char *format, ...);
//#define DEBUG_LOG DEBUG_LOG_IMPL

bool handleNewClient(WiFiServer &server, WiFiClient clients[]);
int pushClient(WiFiClient* client, uint8_t B);
int pushClient(WiFiClient* client, uint8_t B);
void handleClient(WiFiClient* client);

int pushEnhClient(WiFiClient* client, uint8_t B);
void handleEnhClient(WiFiClient* client);

#endif
6 changes: 5 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ build_flags =
lib_deps =
https://github.com/tzapu/WiFiManager
https://github.com/marvinroger/ESP8266TrueRandom
https://github.com/guido4096/espsoftwareserial.git#add-startbit-timestamp

[env:esp12e-ota]
extends = env:esp12e
Expand All @@ -48,6 +49,9 @@ platform = espressif32
board = esp32-c3-devkitm-1
build_flags =
-DRESET_PIN=20
lib_deps =
https://github.com/tzapu/WiFiManager
https://github.com/guido4096/espsoftwareserial.git#add-startbit-timestamp

[env:esp32-c3-ota]
extends = env:esp32-c3
Expand All @@ -57,4 +61,4 @@ upload_protocol = espota
[env:esp32-c3-ota-vpn]
extends = env:esp32-c3-ota
upload_protocol = custom
upload_command = scp $SOURCE [email protected]:firmware.bin && ssh [email protected] espota.py -i esp-ebus.local -p 3232 -f firmware.bin -d -r
upload_command = scp $SOURCE [email protected]:firmware.bin && ssh [email protected] espota.py -i esp-ebus.local -p 3232 -f firmware.bin -d -r
Loading

0 comments on commit 5f9b72a

Please sign in to comment.