Compare commits

..

No commits in common. "e10a4929107f61cbad01683d5866611c5cdd6fef" and "3e50e2bfb499e0e14f2ae1a2d39fdacf5986e503" have entirely different histories.

10 changed files with 20 additions and 287 deletions

View File

@ -1,12 +1,9 @@
{ {
"dependencies": { "dependencies": {
"@roamhq/wrtc": "0.8.0", "@roamhq/wrtc": "0.8.0",
"node-dht-sensor": "^0.4.5",
"node-pre-gyp": "^0.17.0", "node-pre-gyp": "^0.17.0",
"pigpio": "^3.3.1", "ws": "^8.18.0",
"sass": "1.86.3", "sass": "1.86.3"
"serialport": "^13.0.0",
"ws": "^8.18.0"
}, },
"scripts": { "scripts": {
"build:scss": "npx sass src/static/css:dist/static/css", "build:scss": "npx sass src/static/css:dist/static/css",
@ -19,7 +16,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.2", "@types/node": "^22.10.2",
"@types/node-dht-sensor": "^0.4.2",
"@types/ws": "^8.5.13", "@types/ws": "^8.5.13",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.2" "typescript": "^5.8.2"

View File

@ -1,16 +0,0 @@
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

@ -1,7 +1,6 @@
import * as http from "http"; import * as http from "http";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import IO, { ISensors } from "./io";
export default class HttpServer { export default class HttpServer {
@ -11,7 +10,7 @@ export default class HttpServer {
private root: string; private root: string;
// public constructor(port: number, root: string, tune: (ch: string, adp?: number) => void, getChannels: ()=>string[], getSignal: (adapter:number)=>object) { // public constructor(port: number, root: string, tune: (ch: string, adp?: number) => void, getChannels: ()=>string[], getSignal: (adapter:number)=>object) {
public constructor(port: number, root: string, io: IO) { public constructor(port: number, root: string) {
this.port = port; this.port = port;
this.root = root; this.root = root;
this.httpServer = http.createServer((req, res) => { this.httpServer = http.createServer((req, res) => {
@ -26,8 +25,8 @@ export default class HttpServer {
switch (req.method) { switch (req.method) {
case "GET": case "GET":
switch (api) { switch (api) {
case "sensors": case "list":
body = JSON.stringify(io.getSensors()); // body = JSON.stringify(getChannels());
status = 200; status = 200;
break; break;
case "signal": case "signal":
@ -40,7 +39,7 @@ export default class HttpServer {
break; break;
case "PUT": case "PUT":
switch (api) { switch (api) {
case "power": case "tune":
const channel = decodeURIComponent(query[3]); const channel = decodeURIComponent(query[3]);
const adapter = parseInt(url.searchParams.get('adapter')); const adapter = parseInt(url.searchParams.get('adapter'));
// tune(channel, adapter); // tune(channel, adapter);

View File

@ -1,78 +0,0 @@
import Serial from "./serial";
import * as pigpio from 'pigpio';
import * as dht from 'node-dht-sensor';
type ONOFF = 0 | 1;
const ON: ONOFF = 0
const OFF: ONOFF = 1
export interface ISensors {
temperature: number;
lights: boolean;
heat: boolean
moisture0: number;
moisture1: number;
humidity: number;
}
export default class IO {
gpioLights: pigpio.Gpio
gpioHeat: pigpio.Gpio
serial: Serial;
lights: boolean;
heat: boolean;
getMoisture: () => ({ moisture0: number, moisture1: number });
public constructor(LIGHTS_GPIO = 17, HEAT_GPIO=22, DHT_GPIO = 27, DHT_MODEL = 22) {
const serial = new Serial()
this.getMoisture = serial.getMoisture.bind(serial);
this.serial = serial;
const readLights = new pigpio.Gpio(LIGHTS_GPIO, { mode: pigpio.Gpio.INPUT });
this.lights = readLights.digitalRead() == ON ? true : false
this.gpioLights = new pigpio.Gpio(LIGHTS_GPIO, { mode: pigpio.Gpio.OUTPUT });
const readHeat = new pigpio.Gpio(HEAT_GPIO, { mode: pigpio.Gpio.INPUT });
this.lights = readHeat.digitalRead() == ON ? true : false
this.gpioHeat = new pigpio.Gpio(HEAT_GPIO, { mode: pigpio.Gpio.OUTPUT });
dht.initialize(22, DHT_GPIO);
dht.setMaxRetries(10);
}
public setPower(state: boolean, GPIO= 17) {
if(GPIO == 17){
this.lights = state;
this.gpioLights.digitalWrite(this.lights ? ON : OFF);
}
else if(GPIO == 22){
this.heat = state;
this.gpioHeat.digitalWrite(this.heat ? ON : OFF);
}
}
// public togglePower() {
// this.power = !this.power;
// this.gpio.digitalWrite(this.power ? ON : OFF);
// }
private round = (n) =>
(n * 100) / 100
public getSensors(): ISensors {
const { temperature, humidity } = dht.read(22, 27)
const { moisture0, moisture1 } = this.getMoisture()
return {
lights: this.lights,
heat: this.heat,
moisture0: moisture0,
moisture1: moisture1,
temperature: this.round(temperature),
humidity: this.round(humidity)
}
}
}

View File

@ -1,47 +0,0 @@
import * as serialport from 'serialport';
// interface ISerial {
// moisture: number;
// parser: serialport.ReadlineParser
// }
export default class Serial {
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 data = JSON.parse(line)
const moisture0 = parseInt(data['A0'], 10);
const moisture1 = parseInt(data['A1'], 10);
if (!isNaN(moisture0) && !isNaN(moisture1)) {
this.moisture0 = moisture0;
this.moisture1 = moisture1;
}
else {
console.error("Error reading analog data")
}
});
port.on('error', err => {
console.error('Serial Error:', err.message);
});
}
private scale = (value: number) => {
const percent = (value / 1024) * 100;
return Math.min(Math.max(percent, 0), 100); // Clamp to 0-100 just in case
}
public getMoisture() {
return { moisture0: this.scale(this.moisture0), moisture1: this.scale(this.moisture1) };
}
}

View File

@ -1,5 +1,4 @@
import HttpServer from './http'; import HttpServer from './http';
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;
@ -25,16 +24,8 @@ const TV_DEV_0 = process.env.TV_DEV_0 ?? '/dev/video0'
// const getSignal = (adapter: number) => // const getSignal = (adapter: number) =>
// zap.getSignal(adapter) // zap.getSignal(adapter)
const io = new IO(); const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT);
const getSensors: ()=>ISensors = io.getSensors.bind(io); const videoSocket = new VideoSocket(WS_PORT, TV_DEV_0);
// setTimeout(()=>{
// console.log("sen ",io.getSensors())
// console.log("sen2 ",getSensors())
// },200)
// 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, getSensors);
httpServer.start(); httpServer.start();
@ -42,36 +33,17 @@ httpServer.start();
process.stdin.setEncoding("utf8"); process.stdin.setEncoding("utf8");
process.stdin.resume(); process.stdin.resume();
console.log("Menu:\n1)Lights Power off\n2) Lights Power on\n3)Heat Power Off\n4) Heat power on\n5)Read Sensors"); console.log("Menu:");
process.stdin.on("data", async (data: string) => { process.stdin.on("data", async (data: string) => {
const input = data.trim(); const input = data.trim();
console.log(`Received: "${input}"`); console.log(`Received: "${input}"`);
const val = parseInt(input); // await zap.zapTo(input).then((zap: IZap) => {
switch(val) { // console.log(`Tuned ${zap.adapter} to ${zap.channel}`)
case 0:
break; // }).catch((err: Error) => {
case 1: // console.error(err.message);
io.setPower(false,17); // });
break;
case 2:
io.setPower(true,17);
break;
case 3:
io.setPower(false,22);
break;
case 4:
io.setPower(true,22);
break;
case 5:
console.log("a ", getSensors())
break;
default:
console.log("No option for "+input)
}
}); });

View File

@ -18,18 +18,12 @@
<video id="video0" autoplay playsinline controls></video> <video id="video0" autoplay playsinline controls></video>
<div id="channel-container-0" class="channel-group"></div> <div id="channel-container-0" class="channel-group"></div>
</div> </div>
<div id="data-container">
<!-- <p id="power"></p>
<p id="moisture"></p>
<p id="temperature"></p>
<p id="humidity"></p> -->
</div>
</main> </main>
</div> </div>
<script src="js/video.js"></script> <script src="js/video.js"></script>
<script src="js/api.js"></script>
</body> </body>
</html> </html>

View File

@ -1,9 +0,0 @@
const getSensors = () =>{
}
const poll = () =>{
fetch("/api/sensors").then(r=>r.json()).then(data =>{
console.log("data ",data)
})
}

View File

@ -2,24 +2,16 @@ const isSecure = window.location.protocol === 'https:';
const host = window.location.hostname; const host = window.location.hostname;
const ws0builder = isSecure ? `wss://${host}/ws3` : `ws://${host}:3003`; const ws0builder = isSecure ? `wss://${host}/ws3` : `ws://${host}:3003`;
const ws0 = new WebSocket(ws0builder); const ws0 = new WebSocket(ws0builder);
interface IData {
power: boolean,
moisture: number,
temperature: number,
humidity: number
}
const dataContainer = document.getElementById('data-container') as HTMLDivElement
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(isSecure ? config : {}); const pc0 = new RTCPeerConnection(config);
const video0 = document.getElementById('video0') as HTMLVideoElement; const video0 = document.getElementById('video0') as HTMLVideoElement;
@ -34,27 +26,6 @@ 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);
const json = JSON.parse(event.data) ;
dataContainer.innerHTML = Object.entries(json).map(([k,v])=>`<p>${k}:${v}</p>`).join('')
};
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' })
@ -66,7 +37,6 @@ 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') {
@ -74,4 +44,3 @@ ws0.onmessage = async (message) => {
} }
}; };

View File

@ -1,7 +1,6 @@
import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection, RTCDataChannel } from '@roamhq/wrtc'; import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection } 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
@ -10,7 +9,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, getSensors: () => ISensors) { public constructor(port: number, videoDevice) {
this.videoDevice = videoDevice this.videoDevice = videoDevice
const ffmpegProcess = this.startFFmpeg(); const ffmpegProcess = this.startFFmpeg();
const videoTrack = this.createVideoTrack(ffmpegProcess); const videoTrack = this.createVideoTrack(ffmpegProcess);
@ -26,35 +25,11 @@ 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);
@ -206,28 +181,6 @@ 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: [