Skip to content

Commit fef369e

Browse files
committed
websocket: initial proof of concept
1 parent f497ad6 commit fef369e

File tree

2 files changed

+320
-0
lines changed

2 files changed

+320
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
2+
#include <Servo.h>
3+
#include <Wire.h>
4+
#include <Firmata.h>
5+
6+
#include <ESP8266mDNS.h> // esp8266 Arduino standard library
7+
#include <WebSocketsServer.h> // https://github.com/Links2004/arduinoWebSockets
8+
#include <PipedStream.h> // https://github.com/paulo-raca/ArduinoBufferedStreams
9+
10+
#include <utility/ExampleStandardFirmataCommon.h>
11+
12+
#define DEBUG_ENABLED 0 // 0 or 1
13+
14+
#if DEBUG_ENABLED
15+
#define DEBUG(x...) do { x; } while (0)
16+
#define IS_IGNORE_PIN(i) ((i) == 1) // 1 == Serial TX
17+
#else
18+
#define DEBUG(x...) do { (void)0; } while (0)
19+
#endif
20+
21+
PipedStreamPair pipe; // streamify data coming from websocket
22+
PipedStream& StreamFirmataInternal = pipe.first;
23+
PipedStream& StreamAsFirmata = pipe.second;
24+
WebSocketsServer webSocket(3031);
25+
26+
/*
27+
* StandardFirmataWiFi communicates with WiFi shields over SPI. Therefore all
28+
* SPI pins must be set to IGNORE. Otherwise Firmata would break SPI communication.
29+
* Additional pins may also need to be ignored depending on the particular board or
30+
* shield in use.
31+
*/
32+
void ignorePins()
33+
{
34+
#ifdef IS_IGNORE_PIN
35+
for (byte i = 0; i < TOTAL_PINS; i++) {
36+
if (IS_IGNORE_PIN(i)) {
37+
Firmata.setPinMode(i, PIN_MODE_IGNORE);
38+
}
39+
}
40+
#endif
41+
}
42+
43+
void initTransport()
44+
{
45+
WiFi.mode(WIFI_STA);
46+
WiFi.begin(STASSID, STAPSK);
47+
DEBUG(Serial.printf("Connecting to SSID '%s'...\n", STASSID));
48+
while (WiFi.status() != WL_CONNECTED)
49+
{
50+
delay(1000);
51+
DEBUG(Serial.print('.'));
52+
}
53+
DEBUG(Serial.printf("Connected, IP address: %s\n", WiFi.localIP().toString().c_str()));
54+
}
55+
56+
int clients = 0;
57+
58+
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length)
59+
{
60+
DEBUG(Serial.printf("[ws:%u,t:%d,l:%z]: ", num, (int)type, length));
61+
62+
switch(type) {
63+
64+
case WStype_DISCONNECTED:
65+
DEBUG(Serial.println("Disconnected!"));
66+
clients--;
67+
break;
68+
69+
case WStype_CONNECTED:
70+
{
71+
DEBUG(Serial.printf("Connected from %s url:%s\n", webSocket.remoteIP(num).toString().c_str(), (const char*)payload));
72+
webSocket.sendTXT(num, "Firmata WebSocket Server ready for your network/wireless service");
73+
clients++;
74+
}
75+
break;
76+
77+
case WStype_TEXT:
78+
DEBUG(Serial.printf("Informative Text from WebSocket: '%s'\n", (const char*)payload));
79+
break;
80+
81+
case WStype_BIN:
82+
DEBUG(Serial.printf("binary from WebSocket\n"));
83+
#if 1
84+
// !! there is a Print::write(ptr,size_t) !?
85+
//DEBUG(Serial.print("before: ");
86+
//DEBUG(Serial.println(StreamFirmataInternal.available());
87+
for (size_t i = 0; i < length; i++)
88+
{
89+
//Serial.printf("0x%02x ", (int)payload[i]);
90+
if (StreamAsFirmata.write(payload[i]) != 1) {
91+
DEBUG(Serial.println("stream error writing to StreamAsFirmata"));
92+
}
93+
}
94+
//DEBUG(Serial.println();
95+
//DEBUG(Serial.print("after: ");
96+
//DEBUG(Serial.println(StreamFirmataInternal.available());
97+
#else
98+
if (StreamAsFirmata.write(payload, length) != length) {
99+
DEBUG(Serial.println("stream error writing to StreamAsFirmata"));
100+
}
101+
#endif
102+
break;
103+
104+
default:
105+
DEBUG(Serial.println("TODO: unhandled WS message type"));
106+
}
107+
}
108+
109+
void initWebSocket()
110+
{
111+
webSocket.begin();
112+
webSocket.onEvent(webSocketEvent);
113+
DEBUG(Serial.println("WS: initialized"));
114+
}
115+
116+
void initFirmata()
117+
{
118+
initFirmataCommonBegin();
119+
ignorePins();
120+
121+
// Using the pipes stream
122+
// it is written to by the websocket event function
123+
// it is read from by the main loop
124+
Firmata.begin(StreamFirmataInternal);
125+
126+
initFirmataCommonEnd();
127+
}
128+
129+
void setup()
130+
{
131+
DEBUG(Serial.begin(115200));
132+
133+
initTransport();
134+
initWebSocket();
135+
initFirmata();
136+
MDNS.begin("firmata"); // "firmata.local" will be the dns name in LAN
137+
}
138+
139+
/*==============================================================================
140+
* LOOP()
141+
*============================================================================*/
142+
void loop()
143+
{
144+
loopFirmataCommon();
145+
webSocket.loop();
146+
MDNS.update();
147+
148+
size_t StreamAsFirmataLen = StreamAsFirmata.available();
149+
150+
#if DEBUG_ENABLED
151+
static unsigned long last = 0;
152+
if (millis() - last > 1000)
153+
{
154+
last += 1000;
155+
DEBUG(Serial.printf("Firmata to WS: available=%zd --- WS to Firmata: available=%zd\n",
156+
StreamAsFirmataLen,
157+
StreamFirmataInternal.available()));
158+
}
159+
#endif
160+
161+
if (clients && StreamAsFirmataLen)
162+
{
163+
DEBUG(Serial.printf("Firmata -> WS: %zd bytes", StreamAsFirmataLen));
164+
165+
static byte tempBuffer [MAX_DATA_BYTES];
166+
if (StreamAsFirmataLen > sizeof(tempBuffer))
167+
StreamAsFirmataLen = sizeof(tempBuffer);
168+
if ( StreamAsFirmata.readBytes(tempBuffer, StreamAsFirmataLen) == StreamAsFirmataLen
169+
&& webSocket.broadcastBIN(tempBuffer, StreamAsFirmataLen))
170+
{
171+
DEBUG(Serial.printf("Successfully broadcasted-to-websocket a binary message of %zd bytes\n", StreamAsFirmataLen));
172+
}
173+
else
174+
{
175+
DEBUG(Serial.printf("Error broadcasting-to-websocket a binary message of %zd bytes", StreamAsFirmataLen));
176+
}
177+
}
178+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<!DOCTYPE HTML>
2+
3+
<html>
4+
<head>
5+
<script type = "text/javascript">
6+
7+
// message command bytes (128-255/0x80-0xFF)
8+
9+
const DIGITAL_MESSAGE = 0x90; // send data for a digital port (collection of 8 pins)
10+
const ANALOG_MESSAGE = 0xE0; // send data for an analog pin (or PWM)
11+
const REPORT_ANALOG = 0xC0; // enable analog input by pin #
12+
const REPORT_DIGITAL = 0xD0; // enable digital input by port pair
13+
//
14+
const SET_PIN_MODE = 0xF4; // set a pin to INPUT/OUTPUT/PWM/etc
15+
const SET_DIGITAL_PIN_VALUE = 0xF5; // set value of an individual digital pin
16+
//
17+
const REPORT_VERSION = 0xF9; // report protocol version
18+
const SYSTEM_RESET = 0xFF; // reset from MIDI
19+
//
20+
const START_SYSEX = 0xF0; // start a MIDI Sysex message
21+
const END_SYSEX = 0xF7; // end a MIDI Sysex message
22+
23+
// extended command set using sysex (0-127/0x00-0x7F)
24+
/* 0x00-0x0F reserved for user-defined commands */
25+
26+
const SERIAL_DATA = 0x60; // communicate with serial devices, including other boards
27+
const ENCODER_DATA = 0x61; // reply with encoders current positions
28+
const SERVO_CONFIG = 0x70; // set max angle, minPulse, maxPulse, freq
29+
const STRING_DATA = 0x71; // a string message with 14-bits per char
30+
const STEPPER_DATA = 0x72; // control a stepper motor
31+
const ONEWIRE_DATA = 0x73; // send an OneWire read/write/reset/select/skip/search request
32+
const SHIFT_DATA = 0x75; // a bitstream to/from a shift register
33+
const I2C_REQUEST = 0x76; // send an I2C read/write request
34+
const I2C_REPLY = 0x77; // a reply to an I2C read request
35+
const I2C_CONFIG = 0x78; // config I2C settings such as delay times and power pins
36+
const REPORT_FIRMWARE = 0x79; // report name and version of the firmware
37+
const EXTENDED_ANALOG = 0x6F; // analog write (PWM, Servo, etc) to any pin
38+
const PIN_STATE_QUERY = 0x6D; // ask for a pin's current mode and value
39+
const PIN_STATE_RESPONSE = 0x6E; // reply with pin's current mode and value
40+
const CAPABILITY_QUERY = 0x6B; // ask for supported modes and resolution of all pins
41+
const CAPABILITY_RESPONSE = 0x6C; // reply with supported modes and resolution
42+
const ANALOG_MAPPING_QUERY = 0x69; // ask for mapping of analog to pin numbers
43+
const ANALOG_MAPPING_RESPONSE = 0x6A; // reply with mapping info
44+
const SAMPLING_INTERVAL = 0x7A; // set the poll rate of the main loop
45+
const SCHEDULER_DATA = 0x7B; // send a createtask/deletetask/addtotask/schedule/querytasks/querytask request to the scheduler
46+
const SYSEX_NON_REALTIME = 0x7E; // MIDI Reserved for non-realtime messages
47+
const SYSEX_REALTIME = 0x7F; // MIDI Reserved for realtime messages
48+
49+
// pin modes
50+
const PIN_MODE_INPUT = 0x00; // same as INPUT defined in Arduino.h
51+
const PIN_MODE_OUTPUT = 0x01; // same as OUTPUT defined in Arduino.h
52+
const PIN_MODE_ANALOG = 0x02; // analog pin in analogInput mode
53+
const PIN_MODE_PWM = 0x03; // digital pin in PWM output mode
54+
const PIN_MODE_SERVO = 0x04; // digital pin in Servo output mode
55+
const PIN_MODE_SHIFT = 0x05; // shiftIn/shiftOut mode
56+
const PIN_MODE_I2C = 0x06; // pin included in I2C setup
57+
const PIN_MODE_ONEWIRE = 0x07; // pin configured for 1-wire
58+
const PIN_MODE_STEPPER = 0x08; // pin configured for stepper motor
59+
const PIN_MODE_ENCODER = 0x09; // pin configured for rotary encoders
60+
const PIN_MODE_SERIAL = 0x0A; // pin configured for serial communication
61+
const PIN_MODE_PULLUP = 0x0B; // enable internal pull-up resistor for pin
62+
const PIN_MODE_IGNORE = 0x7F; // pin configured to be ignored by digitalWrite and capabilityResponse
63+
64+
const TOTAL_PIN_MODES = 13;
65+
66+
var server = "ws://firmata.local:3031";
67+
var ws = undefined;
68+
69+
function WebSocketTest() {
70+
71+
console.log("hello");
72+
document.body.addEventListener('click', clickEvent, true);
73+
74+
if (!("WebSocket" in window))
75+
{
76+
alert("WebSocket NOT supported by your Browser!");
77+
return;
78+
}
79+
80+
//var ws = new WebSocket("ws://firmata.local:3031");
81+
ws = new WebSocket("ws://10.0.1.30:3031");
82+
if (!ws)
83+
{
84+
alert("Cannot open WebSocket " + server);
85+
return;
86+
}
87+
88+
ws.onopen = function() {
89+
console.log('connected to server\n');
90+
ws.send("hello from ws");
91+
};
92+
93+
ws.onmessage = function (evt) {
94+
var len = evt.data.length;
95+
console.log('type=' + typeof(evt.data));
96+
if (evt.data instanceof ArrayBuffer)
97+
{
98+
console.log('receive binary event, len=' + evt.data.length + '\n');
99+
for (var i = 0; i < evt.data.length; i++)
100+
console.log(' 0x' + evt.data[i].toString(16));
101+
console.log('\n');
102+
}
103+
else
104+
{
105+
console.log('receive text event len=' + evt.data.length + ': "' + evt.data + '"\n');
106+
}
107+
//ws.send(evt.data);
108+
};
109+
110+
ws.onclose = function() {
111+
console.log('ws closed\n');
112+
ws = undefined;
113+
};
114+
}
115+
116+
function clickEvent (n)
117+
{
118+
console.log("click" + n);
119+
ws.send("click" + n.toString());
120+
b = new Uint8Array(4);
121+
b[0] = DIGITAL_MESSAGE;
122+
b[1] = 1 << n;
123+
b[2] = 1;
124+
b[3] = 0;
125+
console.log('length to send = ' + b.byteLength + ': ' + b);
126+
ws.send(b);
127+
}
128+
129+
130+
</script>
131+
</head>
132+
133+
<body onload="WebSocketTest()">
134+
<button onclick="clickEvent(0)">toggle 0</button>
135+
<button onclick="clickEvent(1)">toggle 1</button>
136+
<button onclick="clickEvent(2)">toggle 2</button>
137+
<button onclick="clickEvent(3)">toggle 3</button>
138+
<button onclick="clickEvent(4)">toggle 4</button>
139+
140+
</body>
141+
142+
</html>

0 commit comments

Comments
 (0)