Skip to content

Instantly share code, notes, and snippets.

@JDeeth
Created May 8, 2023 02:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JDeeth/3cdfeb2de74b30afe751021f101ba2ec to your computer and use it in GitHub Desktop.
Save JDeeth/3cdfeb2de74b30afe751021f101ba2ec to your computer and use it in GitHub Desktop.
This is most of the source files for the ESP8266-based SimSig exploratory work I was doing a few months ago before getting distracted. (`Credentials.h` had my wifi details and SimSig username/password hardcoded. Don't do that. Use the WiFiManager library for ESP boards instead.) Very early days - you'll see it's hard-coded for Alrewas level cros…
#pragma once
#include <Ticker.h>
class Blinker {
public:
Blinker(float freq) : _interval{freq / 2} {}
void setup() {
_ticker.attach(_interval, [this] { this->_toggle(); });
}
bool blinkNow() const { return _lit; }
private:
Ticker _ticker;
float _interval;
bool _lit{false};
void _toggle() { _lit = !_lit; }
};
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <SPI.h>
class Display : public Adafruit_PCD8544 {
public:
Display(int8_t SCLK, int8_t DIN, int8_t DC, int8_t RST)
: Adafruit_PCD8544(SCLK, DIN, DC, RST) {}
void setup() {
begin(80);
setRotation(2);
clearDisplay();
display();
delay(50);
setTextColor(BLACK);
setTextSize(1);
setCursor(0, 0);
display();
}
};
#pragma once
#include <Bounce2.h>
class LedButton : public Bounce2::Button {
public:
LedButton(
uint8_t led_pin, uint8_t button_pin,
std::function<bool()> blinkTimer = [] { return true; })
: _button_pin{button_pin}, _led_pin{led_pin}, _blinkTimer{blinkTimer} {}
void setup() {
attach(_button_pin, INPUT_PULLUP);
setPressedState(LOW);
pinMode(_led_pin, OUTPUT);
}
enum class State { OFF, BLINK, STEADY };
bool update() {
bool target{false};
switch (_state) {
case State::OFF:
target = false;
break;
case State::BLINK:
target = _blinkTimer();
break;
case State::STEADY:
target = true;
break;
}
if (_lit != target) {
_lit = target;
digitalWrite(_led_pin, _lit);
}
return Bounce2::Button::update();
}
State state() const { return _state; }
void set(State state) { _state = state; }
void steady() { set(State::STEADY); }
void off() { set(State::OFF); }
void blink() { set(State::BLINK); }
void toggle() { set(_state == State::OFF ? State::STEADY : State::OFF); }
private:
const uint8_t _button_pin;
const uint8_t _led_pin;
std::function<bool()> _blinkTimer;
State _state{State::BLINK};
bool _lit{false};
};
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include "SimSig.h"
#include "Blinker.h"
#include "Credentials.h"
#include "LedButton.h"
#include "Display5110.h"
Display display{D5, D7, D1, D2};
Blinker blinkTimer{1.15};
LedButton raise = LedButton(1, 3, [] { return blinkTimer.blinkNow(); });
LedButton lower = LedButton(D8, D6, [] { return blinkTimer.blinkNow(); });
LedButton clear = LedButton(D3, D0, [] { return blinkTimer.blinkNow(); });
SimSig simsig = SimSig();
void wifiBeginVerbose(const char* ssid, const char* wpa2);
void aston_demo();
void setup() {
Serial.begin(115200);
Serial.println();
delay(500);
display.setup();
display.print("Hello world");
display.display();
blinkTimer.setup();
// raise.setup();
lower.setup();
clear.setup();
wifiBeginVerbose(WLAN_SSID, WLAN_PASS);
simsig.connect("192.168.178.36",
51515U,
SimSig::Credentials{SIMSIG_USERNAME, SIMSIG_PASSWORD});
}
void loop() {
if (simsig.connected()) {
switch (simsig.alrewas) {
case 0: // raised
raise.steady();
lower.off();
clear.off();
break;
case 1: // lowering
raise.off();
lower.blink();
clear.off();
break;
case 2: // down
raise.off();
lower.steady();
clear.blink();
break;
case 3: // clear
raise.off();
lower.steady();
clear.steady();
break;
case 4: // raising
raise.blink();
lower.off();
clear.off();
break;
}
} else {
raise.blink();
lower.blink();
clear.blink();
}
raise.update();
lower.update();
clear.update();
if (raise.pressed()) {
display.clearDisplay();
display.print("Raise");
display.display();
simsig.crossingRequest("GALREWAS", SimSig::CrossingOp::Raise);
}
if (lower.pressed()) {
display.clearDisplay();
display.print("Lower");
display.display();
simsig.crossingRequest("GALREWAS", SimSig::CrossingOp::Lower);
}
if (clear.pressed()) {
display.clearDisplay();
display.print("Clear");
display.display();
simsig.crossingRequest("GALREWAS", SimSig::CrossingOp::Clear);
}
}
uint8_t counter{0};
void aston_demo() {
// Aston
auto aston_lc_p2_up = "A154";
auto aston_lc_p2_dn = "A163";
char td[5];
snprintf(td, 6, "0Z%02u", counter);
if (counter % 2) {
simsig.interposeTd(aston_lc_p2_up, td);
simsig.cancelTd(aston_lc_p2_dn);
} else {
simsig.interposeTd(aston_lc_p2_dn, td);
simsig.cancelTd(aston_lc_p2_up);
}
counter++;
counter %= 100;
}
void ns_demo() {
// Birmingham New Street
simsig.routeRequest("R149DM");
delay(500);
simsig.routeRequest("R142AM");
delay(3000);
simsig.bpull("S149");
delay(500);
simsig.bpull("S142");
}
void wifiBeginVerbose(const char* ssid, const char* wpa2) {
Serial.println("Logging into WLAN: " + String(ssid));
Serial.print("...");
WiFi.begin(ssid, wpa2);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" success.");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
lib_deps =
thomasfredericks/Bounce2@^2.71
bblanchon/ArduinoJson@^6.20.0
me-no-dev/ESPAsyncTCP@^1.2.2
werecatf/Adafruit PCD8544 Nokia 5110 LCD library@0.0.0-alpha+sha.0c92b10794
adafruit/Adafruit GFX Library@^1.11.4
adafruit/Adafruit BusIO@^1.14.1
Wire
SPI
monitor_speed = 115200
monitor_rts = 0
monitor_dtr = 0
#pragma once
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <optional>
void debug(const char* msg) {
// Serial.println(msg);
// delay(1000);
}
class SimSig {
public:
SimSig() = default;
struct Credentials {
String username;
String password;
String asHeaders() {
return String("login:") + username + String("\npasscode:") + password +
"\n";
}
};
void connect(String host,
uint16_t port,
std::optional<Credentials> credentials = {}) {
debug("Entering connect");
_credentials = credentials;
_client.onConnect(
[](void* obj, AsyncClient* c) {
(static_cast<SimSig*>(obj))->_onConnect();
},
this);
_client.onData(
[](void* obj, AsyncClient* c, void* data, size_t len) {
debug(static_cast<char*>(data));
(static_cast<SimSig*>(obj))->_onData(data, len);
},
this);
_client.connect(host.c_str(), port);
}
void interposeTd(const String& berthId, const String& trainId) {
_json.clear();
auto msg = _json.createNestedObject("cc_msg");
msg["to"] = berthId;
msg["descr"] = trainId;
_send(Topic::ALL_SIG_AREA);
}
void cancelTd(const String& berthId) {
_json.clear();
auto msg = _json.createNestedObject("cb_msg");
msg["from"] = berthId;
_send(Topic::ALL_SIG_AREA);
}
void routeRequest(const String& routeId) {
_json.clear();
auto msg = _json.createNestedObject("routerequest");
msg["route"] = routeId;
_send(Topic::ALL_SIG_AREA);
}
void bpull(const String& signalId) {
_json.clear();
auto msg = _json.createNestedObject("bpull");
msg["signal"] = signalId;
_send(Topic::ALL_SIG_AREA);
}
enum class CrossingOp { Raise, Lower, Clear };
void crossingRequest(const String& crossingId, CrossingOp operation) {
debug("Entering crossingRequest...");
_json.clear();
debug("JSON cleared");
auto request = _json.createNestedObject("crossingrequest");
request["crossing"] = crossingId;
switch (operation) {
case CrossingOp::Raise:
request["operation"] = "raise";
break;
case CrossingOp::Lower:
request["operation"] = "lower";
break;
case CrossingOp::Clear:
request["operation"] = "clear";
break;
}
debug("Payload constructed");
_send(Topic::ALL_SIG_AREA);
}
bool connected() { return _client.connected(); }
uint8_t alrewas{0};
private:
AsyncClient _client;
StaticJsonDocument<0x400> _json;
std::optional<Credentials> _credentials{};
enum class Topic { ALL_SIG_AREA };
void _onConnect() {
debug("Entering onConnect callback");
String stompConnect[] = {
"STOMP",
"accept-version:1.1",
_credentials.has_value() ? _credentials.value().asHeaders() : ""};
_sendLines(stompConnect, 3);
String subscribe[] = {
"SUBSCRIBE", "id:0", "destination:/topic/TD_ALL_SIG_AREA", "ack:auto"};
_sendLines(subscribe, 4);
}
void _onData(void* data, size_t len) {
auto msg = String(static_cast<char*>(data));
msg = msg.substring(msg.lastIndexOf('\n'));
_json.clear();
deserializeJson(_json, msg);
if (!_json.containsKey("SG_MSG"))
return;
if(!_json["SG_MSG"]["obj_id"].as<String>().equals("GALREWAS")) return;
alrewas = _json["SG_MSG"]["state"].as<uint8_t>();
}
void _send(Topic topic) {
debug("Entered _send");
String destination = "";
switch (topic) {
case Topic::ALL_SIG_AREA:
destination = "/topic/TD_ALL_SIG_AREA";
break;
}
debug("Destination constructed");
auto payload = _json.as<String>();
String lines[] = {
"SEND",
String("content-length:") + String(payload.length()),
String("destination:") + destination,
"",
payload,
};
debug("Lines constructed");
_sendLines(lines, 5);
}
void _sendLines(String lines[], uint8_t line_count) {
debug("Entering _sendLines");
String msg = "";
for (uint8_t i = 0; i < line_count; ++i) {
msg += lines[i];
msg += "\n";
}
msg += '\0';
debug("Frame constructed");
_client.add(msg.c_str(), msg.length());
for (int i = 0; i < 50; ++i) {
if (_client.canSend()) {
_client.send();
debug((String("Frame sent on attempt ") + String(i)).c_str());
return;
}
delay(1);
}
debug("Frame not sent");
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment