Created
May 8, 2023 02:23
-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; } | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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()); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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