data channel working

This commit is contained in:
David Westgate 2025-04-18 03:46:42 +01:00
parent fb5301c9fe
commit 22aeed01be
6 changed files with 113 additions and 23 deletions

16
sketch.c Normal file
View File

@ -0,0 +1,16 @@
void setup() {
Serial.begin(9600);
}
void loop() {
int moisture0 = analogRead(A0);
delayMicroseconds(100);
analogRead(A1);
int moisture1 = analogRead(A1);
Serial.print("{\"A0\":");
Serial.print(moisture0);
Serial.print(",\"A1\":");
Serial.print(moisture1);
Serial.println("}");
delay(1000);
}

View File

@ -21,9 +21,11 @@ export default class IO implements ISensors {
gpio: pigpio.Gpio gpio: pigpio.Gpio
serial: Serial; serial: Serial;
power: boolean; power: boolean;
getMoisture : ()=>number;
public constructor(POWER_GPIO = 17) { public constructor(POWER_GPIO = 17) {
const serial = new Serial() const serial = new Serial()
serial.getMoisture.bind(this); this.getMoisture = serial.getMoisture.bind(serial);
this.serial = serial; this.serial = serial;
// Initialize pigpio and the GPIO pin // Initialize pigpio and the GPIO pin
const read = new pigpio.Gpio(POWER_GPIO, {mode: pigpio.Gpio.INPUT}); const read = new pigpio.Gpio(POWER_GPIO, {mode: pigpio.Gpio.INPUT});
@ -35,9 +37,6 @@ export default class IO implements ISensors {
// this.serial.getMoisture.bind(this) // this.serial.getMoisture.bind(this)
} }
getMoisture() {
return
}
// getPower() { // getPower() {
// // Read the current state of the GPIO pin // // Read the current state of the GPIO pin
@ -52,16 +51,15 @@ export default class IO implements ISensors {
public togglePower() { public togglePower() {
// Toggle the power state (turn the pin on or off) // Toggle the power state (turn the pin on or off)
console.log("toggle")
this.power = !this.power; this.power = !this.power;
this.gpio.digitalWrite(this.power ? ON : OFF); this.gpio.digitalWrite(this.power ? ON : OFF);
} }
public getSensors(): ISensors { public getSensors(): ISensors {
console.log("serial ", this.serial) // console.log("serial ", )
return { return {
power: this.power, power: this.power,
moisture: 0, moisture: this.getMoisture(),
temperature: this.temperature, temperature: this.temperature,
humidity: this.humidity humidity: this.humidity
} }

View File

@ -6,16 +6,25 @@ import * as serialport from 'serialport';
// } // }
export default class Serial { export default class Serial {
private moisture: number; private moisture0: number;
private moisture1: number;
public constructor(path = '/dev/ttyUSB0', baudRate = 9600, delimiter = '\n') { public constructor(path = '/dev/ttyUSB0', baudRate = 9600, delimiter = '\n') {
const port = new serialport.SerialPort({ path, baudRate }); const port = new serialport.SerialPort({ path, baudRate });
const readline = new serialport.ReadlineParser({ delimiter }); const readline = new serialport.ReadlineParser({ delimiter });
const parser = port.pipe(readline); const parser = port.pipe(readline);
parser.on('data', line => { parser.on('data', line => {
const moisture = parseInt(line.trim(), 10); // console.log("data line ", line)
if (!isNaN(moisture)) { const data = JSON.parse(line)
this.moisture = moisture const moisture0 = parseInt(data['A0'], 10);
const moisture1 = parseInt(data['A1'], 10);
if (!isNaN(moisture0) && !isNaN(moisture1)) {
// console.log("m ", this.moisture)
this.moisture0 = moisture0;
this.moisture1 = moisture1;
}
else{
console.log("data ",data)
} }
}); });
port.on('error', err => { port.on('error', err => {
@ -23,7 +32,7 @@ export default class Serial {
}); });
} }
public getMoisture() { public getMoisture() {
return this.moisture; return this.moisture0;
} }

View File

@ -1,5 +1,5 @@
import HttpServer from './http'; import HttpServer from './http';
import IO from './io'; import IO, { ISensors } from './io';
import VideoSocket from './ws'; import VideoSocket from './ws';
const HTTP_PORT = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 8080; const HTTP_PORT = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 8080;
@ -26,7 +26,7 @@ const TV_DEV_0 = process.env.TV_DEV_0 ?? '/dev/video0'
// zap.getSignal(adapter) // zap.getSignal(adapter)
const io = new IO(); const io = new IO();
// const getSensors = io.getSensors.bind(this); const getSensors: ()=>ISensors = io.getSensors.bind(io);
// setTimeout(()=>{ // setTimeout(()=>{
// console.log("sen ",io.getSensors()) // console.log("sen ",io.getSensors())
// console.log("sen2 ",getSensors()) // console.log("sen2 ",getSensors())
@ -34,7 +34,7 @@ const io = new IO();
// const setPower = io.setPower.bind(this, true) // const setPower = io.setPower.bind(this, true)
// const togglePower = io.togglePower.bind(this) // const togglePower = io.togglePower.bind(this)
const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT, io); const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT, io);
const videoSocket = new VideoSocket(WS_PORT, TV_DEV_0); const videoSocket = new VideoSocket(WS_PORT, TV_DEV_0, getSensors);
httpServer.start(); httpServer.start();
@ -42,7 +42,7 @@ httpServer.start();
process.stdin.setEncoding("utf8"); process.stdin.setEncoding("utf8");
process.stdin.resume(); process.stdin.resume();
console.log("Menu:\n1) Power off\n2)Power on\n3) Power flop\n4)Read moisture"); console.log("Menu:\n1) Power off\n2)Power on\n3) Power flop\n4)Read Sensors");
process.stdin.on("data", async (data: string) => { process.stdin.on("data", async (data: string) => {
const input = data.trim(); const input = data.trim();
@ -53,7 +53,7 @@ process.stdin.on("data", async (data: string) => {
break; break;
case 1: case 1:
io.setPower(false);// console.log(io.getMoisture()); io.setPower(false);
break; break;
case 2: case 2:
io.setPower(true); io.setPower(true);
@ -62,7 +62,8 @@ process.stdin.on("data", async (data: string) => {
io.togglePower(); io.togglePower();
break; break;
case 4: case 4:
io.getSensors()
console.log("a ", getSensors())
break; break;
default: default:
console.log("No option for "+input) console.log("No option for "+input)

View File

@ -5,13 +5,13 @@ const ws0 = new WebSocket(ws0builder);
const config = { const config = {
iceServers: [ iceServers: [
{ {
urls: ['stun:dwestgate.us:3478','turn:dwestgate.us:3478?transport=udp'], urls: ['stun:dwestgate.us:3478', 'turn:dwestgate.us:3478?transport=udp'],
username: 'webrtcuser', username: 'webrtcuser',
credential: 'webrtccred' credential: 'webrtccred'
} }
] ]
}; };
const pc0 = new RTCPeerConnection(config); const pc0 = new RTCPeerConnection(isSecure ? config: {});
const video0 = document.getElementById('video0') as HTMLVideoElement; const video0 = document.getElementById('video0') as HTMLVideoElement;
@ -26,6 +26,23 @@ pc0.onicecandidate = ({ candidate }) => {
} }
}; };
// Create the data channel (client initiates)
const dataChannel = pc0.createDataChannel('sensors');
console.log("📡 Data channel created by client");
dataChannel.onopen = () => {
console.log('📬 Client: Data channel opened');
};
dataChannel.onmessage = (event) => {
console.log("📦 Client received message:", event.data);
};
dataChannel.onclose = () => {
console.log("📴 Client: Data channel closed");
};
ws0.onopen = async () => { ws0.onopen = async () => {
pc0.addTransceiver('video', { direction: 'recvonly' }); pc0.addTransceiver('video', { direction: 'recvonly' });
pc0.addTransceiver('audio', { direction: 'recvonly' }) pc0.addTransceiver('audio', { direction: 'recvonly' })
@ -37,6 +54,7 @@ ws0.onopen = async () => {
ws0.onmessage = async (message) => { ws0.onmessage = async (message) => {
const msg = JSON.parse(message.data); const msg = JSON.parse(message.data);
if (msg.type === 'answer') { if (msg.type === 'answer') {
// pc0.data
await pc0.setRemoteDescription(msg.data); await pc0.setRemoteDescription(msg.data);
} }
else if (msg.type === 'ice-candidate') { else if (msg.type === 'ice-candidate') {
@ -44,3 +62,4 @@ ws0.onmessage = async (message) => {
} }
}; };

View File

@ -1,6 +1,7 @@
import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection } from '@roamhq/wrtc'; import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection, RTCDataChannel } from '@roamhq/wrtc';
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
import * as ws from 'ws'; import * as ws from 'ws';
import { ISensors } from './io';
// Constants // Constants
const WIDTH = 640; // Video width const WIDTH = 640; // Video width
@ -9,7 +10,7 @@ const FRAME_SIZE = WIDTH * HEIGHT * 1.5; // YUV420P frame size
export default class VideoSocket { export default class VideoSocket {
videoDevice: string; videoDevice: string;
public constructor(port: number, videoDevice) { public constructor(port: number, videoDevice, getSensors: () => ISensors) {
this.videoDevice = videoDevice this.videoDevice = videoDevice
const ffmpegProcess = this.startFFmpeg(); const ffmpegProcess = this.startFFmpeg();
const videoTrack = this.createVideoTrack(ffmpegProcess); const videoTrack = this.createVideoTrack(ffmpegProcess);
@ -25,11 +26,35 @@ export default class VideoSocket {
wss.on('connection', async (ws: ws.WebSocket) => { wss.on('connection', async (ws: ws.WebSocket) => {
const peerConnection: RTCPeerConnection = this.createPeerConnection(videoTrack, audioTrack); const peerConnection: RTCPeerConnection = this.createPeerConnection(videoTrack, audioTrack);
// The client created the data channel. The server should access it as follows:
peerConnection.ondatachannel = (event) => {
const dataChannel = event.channel; // This is the data channel created by the client
dataChannel.onopen = () => {
console.log('📬 Server: Data channel opened');
// Now you can send data through the channel
setInterval(() => {
const sensorData = getSensors(); // Example function to fetch data
dataChannel.send(JSON.stringify(sensorData)); // Send data to the client
}, 1000);
};
dataChannel.onmessage = (event) => {
console.log("📦 Server received message:", event.data);
};
dataChannel.onclose = () => {
console.log("📴 Server: Data channel closed");
};
};
ws.on('message', async (message: Buffer) => { ws.on('message', async (message: Buffer) => {
const { type, data } = JSON.parse(message.toString()); const { type, data } = JSON.parse(message.toString());
if (type == 'offer') { if (type == 'offer') {
await peerConnection.setRemoteDescription(data); await peerConnection.setRemoteDescription(data);
const answer = await peerConnection.createAnswer(); const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer); await peerConnection.setLocalDescription(answer);
@ -181,6 +206,28 @@ export default class VideoSocket {
return audioSource.createTrack(); return audioSource.createTrack();
} }
createDataChannel = (peerConnection: RTCPeerConnection, getSensors: () => any) => {
const dataChannel = peerConnection.createDataChannel('sensors')
console.log("create data channel");
dataChannel.onopen = () => {
console.log('✅ Data channel is open');
// Send dummy JSON for testing
setInterval(() => {
const sensorData = getSensors(); // Assuming getSensors returns JSON
dataChannel.send(JSON.stringify(sensorData));
}, 1000);
};
dataChannel.onerror = (error) => {
console.error('❌ DataChannel error:', error);
};
dataChannel.onclose = () => {
console.log('❎ DataChannel closed');
};
return peerConnection;
}
createPeerConnection = (videoTrack: MediaStreamTrack, audioTrack: MediaStreamTrack): RTCPeerConnection => { createPeerConnection = (videoTrack: MediaStreamTrack, audioTrack: MediaStreamTrack): RTCPeerConnection => {
const peerConnection = new RTCPeerConnection({ const peerConnection = new RTCPeerConnection({
iceServers: [ iceServers: [