From 22aeed01be533e437d890c46efe677dc82d35571 Mon Sep 17 00:00:00 2001 From: David Westgate Date: Fri, 18 Apr 2025 03:46:42 +0100 Subject: [PATCH] data channel working --- sketch.c | 16 +++++++++++++ src/io.ts | 12 +++++----- src/serial.ts | 19 +++++++++++----- src/server.ts | 13 ++++++----- src/static/js/video.ts | 25 ++++++++++++++++++--- src/ws.ts | 51 ++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 113 insertions(+), 23 deletions(-) create mode 100644 sketch.c diff --git a/sketch.c b/sketch.c new file mode 100644 index 0000000..a5db3ad --- /dev/null +++ b/sketch.c @@ -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); +} \ No newline at end of file diff --git a/src/io.ts b/src/io.ts index a55c3fa..355837b 100644 --- a/src/io.ts +++ b/src/io.ts @@ -21,9 +21,11 @@ export default class IO implements ISensors { gpio: pigpio.Gpio serial: Serial; power: boolean; + getMoisture : ()=>number; + public constructor(POWER_GPIO = 17) { const serial = new Serial() - serial.getMoisture.bind(this); + this.getMoisture = serial.getMoisture.bind(serial); this.serial = serial; // Initialize pigpio and the GPIO pin 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) } - getMoisture() { - return - } // getPower() { // // Read the current state of the GPIO pin @@ -52,16 +51,15 @@ export default class IO implements ISensors { public togglePower() { // Toggle the power state (turn the pin on or off) - console.log("toggle") this.power = !this.power; this.gpio.digitalWrite(this.power ? ON : OFF); } public getSensors(): ISensors { - console.log("serial ", this.serial) + // console.log("serial ", ) return { power: this.power, - moisture: 0, + moisture: this.getMoisture(), temperature: this.temperature, humidity: this.humidity } diff --git a/src/serial.ts b/src/serial.ts index e550bb4..67a1594 100644 --- a/src/serial.ts +++ b/src/serial.ts @@ -6,16 +6,25 @@ import * as serialport from 'serialport'; // } export default class Serial { - private moisture: number; + private moisture0: number; + private moisture1: number; public constructor(path = '/dev/ttyUSB0', baudRate = 9600, delimiter = '\n') { const port = new serialport.SerialPort({ path, baudRate }); const readline = new serialport.ReadlineParser({ delimiter }); const parser = port.pipe(readline); parser.on('data', line => { - const moisture = parseInt(line.trim(), 10); - if (!isNaN(moisture)) { - this.moisture = moisture + // console.log("data line ", line) + const data = JSON.parse(line) + 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 => { @@ -23,7 +32,7 @@ export default class Serial { }); } public getMoisture() { - return this.moisture; + return this.moisture0; } diff --git a/src/server.ts b/src/server.ts index 0f2d001..baac545 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,5 @@ import HttpServer from './http'; -import IO from './io'; +import IO, { ISensors } from './io'; import VideoSocket from './ws'; 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) const io = new IO(); -// const getSensors = io.getSensors.bind(this); +const getSensors: ()=>ISensors = io.getSensors.bind(io); // setTimeout(()=>{ // console.log("sen ",io.getSensors()) // console.log("sen2 ",getSensors()) @@ -34,7 +34,7 @@ const io = new IO(); // const setPower = io.setPower.bind(this, true) // const togglePower = io.togglePower.bind(this) 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(); @@ -42,7 +42,7 @@ httpServer.start(); process.stdin.setEncoding("utf8"); 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) => { const input = data.trim(); @@ -53,7 +53,7 @@ process.stdin.on("data", async (data: string) => { break; case 1: - io.setPower(false);// console.log(io.getMoisture()); + io.setPower(false); break; case 2: io.setPower(true); @@ -62,7 +62,8 @@ process.stdin.on("data", async (data: string) => { io.togglePower(); break; case 4: - io.getSensors() + + console.log("a ", getSensors()) break; default: console.log("No option for "+input) diff --git a/src/static/js/video.ts b/src/static/js/video.ts index 675d93e..b046387 100644 --- a/src/static/js/video.ts +++ b/src/static/js/video.ts @@ -2,16 +2,16 @@ const isSecure = window.location.protocol === 'https:'; const host = window.location.hostname; const ws0builder = isSecure ? `wss://${host}/ws3` : `ws://${host}:3003`; const ws0 = new WebSocket(ws0builder); -const config = { +const config = { 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', credential: 'webrtccred' } ] }; -const pc0 = new RTCPeerConnection(config); +const pc0 = new RTCPeerConnection(isSecure ? config: {}); 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 () => { pc0.addTransceiver('video', { direction: 'recvonly' }); pc0.addTransceiver('audio', { direction: 'recvonly' }) @@ -37,6 +54,7 @@ ws0.onopen = async () => { ws0.onmessage = async (message) => { const msg = JSON.parse(message.data); if (msg.type === 'answer') { + // pc0.data await pc0.setRemoteDescription(msg.data); } else if (msg.type === 'ice-candidate') { @@ -44,3 +62,4 @@ ws0.onmessage = async (message) => { } }; + diff --git a/src/ws.ts b/src/ws.ts index ec31802..5f59f7f 100644 --- a/src/ws.ts +++ b/src/ws.ts @@ -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 * as ws from 'ws'; +import { ISensors } from './io'; // Constants const WIDTH = 640; // Video width @@ -9,7 +10,7 @@ const FRAME_SIZE = WIDTH * HEIGHT * 1.5; // YUV420P frame size export default class VideoSocket { videoDevice: string; - public constructor(port: number, videoDevice) { + public constructor(port: number, videoDevice, getSensors: () => ISensors) { this.videoDevice = videoDevice const ffmpegProcess = this.startFFmpeg(); const videoTrack = this.createVideoTrack(ffmpegProcess); @@ -25,11 +26,35 @@ export default class VideoSocket { wss.on('connection', async (ws: ws.WebSocket) => { 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) => { const { type, data } = JSON.parse(message.toString()); if (type == 'offer') { + await peerConnection.setRemoteDescription(data); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); @@ -181,6 +206,28 @@ export default class VideoSocket { 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 => { const peerConnection = new RTCPeerConnection({ iceServers: [