diff --git a/compute-node/README.md b/compute-node/README.md index 193a997..8ef6bf8 100644 --- a/compute-node/README.md +++ b/compute-node/README.md @@ -1,80 +1,75 @@ -# AdNet Protocol - Smart Advertising with IoT + AI +# ๐Ÿง  AdMojo Compute Node: Where Eyeballs Meet Blockchain ๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธโ›“๏ธ -**Flow**: ESP32-CAM โ†’ Stream Video โ†’ Detection using RNN and duplication avoidance using IoU โ†’ Aggregated # of viewers in 5 min durations โ†’ Send to ThingSpeak. +## What Sorcery Is This? ๐Ÿช„ ---- +Welcome, fellow code wizard! You've discovered the magical realm where computer vision and blockchain collide to create undeniable proof that humans actually looked at those ads. Wild, right? -## ๐Ÿ“ธ ESP32-CAM Module: AdNetCamModule +## The Tech Stack of Champions ๐Ÿ† -A tiny AI Thinker ESP32-CAM module powers our eyes-on-the-ground (literally). It streams MJPEG video over Wi-Fi so we can detect who's watching those juicy ads in real-time. +- **Go**: Because life's too short for garbage collection pauses +- **OpenCV**: Computer vision that doesn't require a PhD +- **Ethereum**: Because regular databases are _so_ Web2 +- **Deep Neural Networks**: Basically magic, but with math -### ๐Ÿ”ง Hardware Used -- **Board:** ESP32-CAM (AI Thinker variant with PSRAM) -- **Camera:** OV2640 -- **Status LED Pin:** GPIO 33 +## โœจ Features That Make You Go "Woah" โœจ -### โš™๏ธ Features -- MJPEG video streaming over Wi-Fi (on http://192.168.1.43/) -- Static IP config for consistency -- Status LED blinks while connecting, stays off when ready -- Stream served as `multipart/x-mixed-replace;boundary=123456789000000000000987654321` -- Optimized for high-quality JPEG (UXGA or SVGA) +### ๐Ÿ” AI-powered People Spotting +- **Face Detection**: Our SSD + ResNet model can spot a face faster than you can say "privacy concerns" +- **Clever Deduplication**: Using IoU tracking, we ensure we don't count the same bewildered face twice +- **Configurable Paranoia Level**: Adjust confidence thresholds to your trust issues +- **See The Matrix**: Watch the detector work in real-time with cool green rectangles (sunglasses not included) -### ๐Ÿ“ Code Summary -See `AdNetCamModule.ino` for full source. Highlights: -- Initializes camera with dynamic config (PSRAM-aware) -- Sets up MJPEG stream endpoint `/` -- Streams images using ESP-IDF HTTP server -- Loop does nothing; all work is done in background +### ๐Ÿ”— Blockchain Verification (The Trust Machine) +- **Firmware Fingerprinting**: Each ESP32-CAM device has a unique cryptographic identity +- **Tamper-Evident Metrics**: Try to fake these numbers. Seriously, we dare you +- **Smart Contract Integration**: Your ad metrics are immortalized on the blockchain (until ETH2.0 finally ships) +- **Cryptographic Flex**: ECDSA signatures that would make Satoshi nod in approval -**To Access the Stream:** -```bash -http://192.168.1.43/ # Served MJPEG Stream -``` +## ๐Ÿ” The Verification Dance ---- +1. **Trust, But Verify**: + - We pull cryptographic receipts from each camera + - If the firmware is tampered, the signature won't match + - Blockchain don't lie, people don't lie, only ads lie -## ๐Ÿค– Backend: Go + OpenCV + AI = โœจ - -### ๐Ÿ”„ Flow -1. Go app connects to MJPEG stream from ESP32-CAM -2. Every 5 seconds, it grabs a frame -3. Runs OpenCV DNN face detection (SSD + ResNet) -4. Tracks new viewers (avoids counting duplicates using IoU filtering) -5. Aggregates counts for 5 minutes -6. Sends the count to [ThingSpeak](https://thingspeak.com/) via API - -### ๐Ÿงฉ Features -- Uses GoCV (Go bindings for OpenCV) -- DNN-based detector (ResNet SSD) for better accuracy than Haar -- Deduplicates people using bounding-box IoU tracking -- Configuration via `.env` (stream URL, model paths, thresholds) -- Displays debug video with rectangles and confidence -- Uses ThingSpeak to report proof-of-view metrics - -### ๐Ÿ› โƒฃ How It Works -- On every frame, we detect faces -- We compare new bounding boxes to the previous frame -- If IoU < threshold, itโ€™s a new person (so we count it!) -- Every 5 minutes, we send the total to ThingSpeak - -### ๐Ÿ‘€ Want to See the AI in Action? -Youโ€™ll get a debug window with live rectangles drawn over faces along with confidence scores. +2. **Face-Finding Wizardry**: + - Every 5 seconds: "Got your face!" + - DNN goes brrrrr + - IoU says "Is this the same person as before? ๐Ÿค”" ---- +3. **The Count of Monte Crypto**: + - We tally viewers like a vegas dealer counts cards + - But unlike Vegas, our count is provably fair -## ๐Ÿ“ The Result? -- Transparent, real-world Proof of Views -- Performance-based rewards for Ad Service Providers -- Smarter ads that actually engage people -- All running on open tech you control +4. **Blockchain Immortality**: + - Your metrics go on-chain faster than you can say "gas fees" + - Each transaction says: "These many humans saw this ad, for realsies" + - Cryptographically signed, mathematically sealed, digitally delivered ---- +## ๐Ÿงฉ Smart Contract Spellbook -## โœ… What's Next? -- Plug into smart contracts for dynamic ad payouts -- Integrate cryptography for verifiability on-chain +The `oracle/performance_oracle.go` contains incantations to communicate with the ethereal realm: -Stay tuned. This is just the beginning. +```go +// Simplified for mere mortals +perfOracle.UpdateMetricsWithSig( + transactor, + deviceID, // Which camera saw the thing + timestamp, // When it saw the thing + faceCount, // How many faces it saw + taps, // How many people tapped (always 0 for now) + firmwareHash, // Cryptographic fingerprint + signature, // Magical seal of authenticity +) +``` + +## ๐Ÿ”ฎ Future Roadmap (When We Get VC Funding) + +- Zero-knowledge proofs (because we actually do care about privacy... kinda) +- Multi-device correlation (creepy, but effective) +- NFT-based ad allocation (because one buzzword isn't enough) +- Decentralized ad marketplace (cutting out the middleman, becoming the middleman) + +--- -Made with โค๏ธ by Anon. \ No newline at end of file +Made with โ˜• caffeine, ๐Ÿค– AI, and a healthy dose of ๐Ÿคฆโ€โ™‚๏ธ debugging \ No newline at end of file diff --git a/compute-node/go.mod b/compute-node/go.mod index 910576f..702eaff 100644 --- a/compute-node/go.mod +++ b/compute-node/go.mod @@ -1,8 +1,37 @@ -module github.com/AnonO6/admojo_module +module github.com/Ethglobal-taipei/Admojo-module/compute-node go 1.24.1 require ( + github.com/ethereum/go-ethereum v1.15.7 github.com/joho/godotenv v1.5.1 gocv.io/x/gocv v0.41.0 ) + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/bits-and-blooms/bitset v1.17.0 // indirect + github.com/consensys/bavard v0.1.22 // indirect + github.com/consensys/gnark-crypto v0.14.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect + github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/supranational/blst v0.3.14 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) diff --git a/compute-node/go.sum b/compute-node/go.sum index 0ed3212..ddd50bc 100644 --- a/compute-node/go.sum +++ b/compute-node/go.sum @@ -1,4 +1,216 @@ +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI= +github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= +github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= +github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= +github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.15.7 h1:vm1XXruZVnqtODBgqFaTclzP0xAvCvQIDKyFNUA1JpY= +github.com/ethereum/go-ethereum v1.15.7/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= +github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= +github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= +github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= +github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= +github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= +github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= +github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= gocv.io/x/gocv v0.41.0 h1:KM+zRXUP28b6dHfhy+4JxDODbCNQNtLg8kio+YE7TqA= gocv.io/x/gocv v0.41.0/go.mod h1:zYdWMj29WAEznM3Y8NsU3A0TRq/wR/cy75jeUypThqU= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/compute-node/main.go b/compute-node/main.go index c4bb353..c08429d 100644 --- a/compute-node/main.go +++ b/compute-node/main.go @@ -1,121 +1,144 @@ package main import ( + "encoding/hex" "fmt" "image" "image/color" + "io" "log" + "math/big" "net/http" "os" "strconv" + "strings" + "sync" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/joho/godotenv" "gocv.io/x/gocv" + + // This would be your abigen-generated package + "github.com/Ethglobal-taipei/Admojo-module/compute-node/oracle" ) -// IoU computes the Intersection over Union of two rectangles. -func IoU(a, b image.Rectangle) float64 { - intersect := a.Intersect(b) - if intersect.Empty() { - return 0.0 - } - interArea := float64(intersect.Dx() * intersect.Dy()) - areaA := float64(a.Dx() * a.Dy()) - areaB := float64(b.Dx() * b.Dy()) - unionArea := areaA + areaB - interArea - return interArea / unionArea +// --------------------------------------------------------------------- +// A. Data Structures +// --------------------------------------------------------------------- +type DeviceConfig struct { + StreamURL string + DeviceID uint64 + TSWriteKey string } +// --------------------------------------------------------------------- +// B. Main Program +// --------------------------------------------------------------------- func main() { - // ------------------------------------------------ - // 1. Load configuration from .env - // ------------------------------------------------ + // Load environment if err := godotenv.Load(".env"); err != nil { - log.Println("Warning: Could not load .env file.") + log.Println("No .env file found or could not load.") } - streamURL := getEnv("ESP32_STREAM_URL", "http://192.168.1.43/") - dnnProtoPath := getEnv("DNN_PROTO_PATH", "./deploy.prototxt") - dnnModelPath := getEnv("DNN_MODEL_PATH", "./res10_300x300_ssd_iter_140000.caffemodel") - dnnConfidenceThresholdStr := getEnv("DNN_CONFIDENCE_THRESHOLD", "0.5") + // Setup intervals + aggSeconds, _ := strconv.Atoi(getEnv("AGGREGATION_INTERVAL", "300")) + aggregationInterval := time.Duration(aggSeconds) * time.Second + + frameSeconds, _ := strconv.Atoi(getEnv("FRAME_INTERVAL", "5")) + frameInterval := time.Duration(frameSeconds) * time.Second + + // Setup DNN thresholds + dnnConfidenceStr := getEnv("DNN_CONFIDENCE_THRESHOLD", "0.5") + dnnConfidence, _ := strconv.ParseFloat(dnnConfidenceStr, 64) + dupThresholdStr := getEnv("DUPLICATE_IOU_THRESHOLD", "0.5") - thingspeakWriteKey := getEnv("THINGSPEAK_WRITE_KEY", "") - thingspeakURL := getEnv("THINGSPEAK_URL", "https://api.thingspeak.com/update") + dupThreshold, _ := strconv.ParseFloat(dupThresholdStr, 64) - aggIntervalSeconds := getEnv("AGGREGATION_INTERVAL", "300") - aggInterval, err := strconv.Atoi(aggIntervalSeconds) - if err != nil { - aggInterval = 300 - } - aggregationInterval := time.Duration(aggInterval) * time.Second + // Setup Ethereum (PerformanceOracle) + transactor, perfOracle := setupEthereum() - frameIntervalSeconds := getEnv("FRAME_INTERVAL", "5") - frameInt, err := strconv.Atoi(frameIntervalSeconds) - if err != nil { - frameInt = 5 + // Load DNN model + protoPath := getEnv("DNN_PROTO_PATH", "./deploy.prototxt") + modelPath := getEnv("DNN_MODEL_PATH", "./res10_300x300_ssd_iter_140000.caffemodel") + net := gocv.ReadNetFromCaffe(protoPath, modelPath) + if net.Empty() { + log.Fatalf("Error loading DNN model from %s / %s\n", protoPath, modelPath) } - frameInterval := time.Duration(frameInt) * time.Second + defer net.Close() - if thingspeakWriteKey == "" { - log.Fatal("THINGSPEAK_WRITE_KEY not set. Please configure .env properly.") + // Gather device configs from .env + devices := readDeviceConfigs() + + // Start a goroutine for each device + var wg sync.WaitGroup + wg.Add(len(devices)) + for _, cfg := range devices { + go func(dc DeviceConfig) { + defer wg.Done() + runDevice(dc, &net, transactor, perfOracle, + aggregationInterval, + frameInterval, + float32(dnnConfidence), + float32(dupThreshold), + ) + }(cfg) } - dnnConfidenceThreshold, err := strconv.ParseFloat(dnnConfidenceThresholdStr, 64) - if err != nil { - dnnConfidenceThreshold = 0.5 - } + log.Println("All device loops started. Press Ctrl+C to stop.") + wg.Wait() + log.Println("All device loops ended. Exiting.") +} - duplicateThreshold, err := strconv.ParseFloat(dupThresholdStr, 64) +// --------------------------------------------------------------------- +// C. runDevice +// --------------------------------------------------------------------- +func runDevice( + cfg DeviceConfig, + net *gocv.Net, + transactor *bind.TransactOpts, + perfOracle *oracle.PerformanceOracle, + aggregationInterval time.Duration, + frameInterval time.Duration, + dnnConfidenceThreshold float32, + duplicateThreshold float32, +) { + // 1) Do an HTTP GET to read firmware headers (X-Firmware-Hash, X-Signature) + firmwareHash, firmwareSig, err := fetchFirmwareHeaders(cfg.StreamURL) if err != nil { - duplicateThreshold = 0.5 + log.Printf("[Device %d] WARNING: Could not fetch firmware headers: %v", cfg.DeviceID, err) + // You might decide to return or keep going. We'll proceed, but the + // on-chain call won't work if these are blank. + } else { + log.Printf("[Device %d] Fetched FirmwareHash=%s, Signature=%s\n", cfg.DeviceID, firmwareHash, firmwareSig) } - // ------------------------------------------------ - // 2. Open the ESP32-CAM MJPEG stream - // ------------------------------------------------ - cap, err := gocv.OpenVideoCapture(streamURL) + // 2) Open the MJPEG stream + cap, err := gocv.OpenVideoCapture(cfg.StreamURL) if err != nil { - log.Fatalf("Error opening video capture from %s: %v", streamURL, err) + log.Printf("[Device %d] ERROR: Failed to open stream %s: %v\n", cfg.DeviceID, cfg.StreamURL, err) + return } defer cap.Close() - // ------------------------------------------------ - // 3. Create a window for visual debugging - // ------------------------------------------------ - window := gocv.NewWindow("DNN Face Detection") + window := gocv.NewWindow(fmt.Sprintf("Device-%d", cfg.DeviceID)) defer window.Close() - // ------------------------------------------------ - // 4. Load the deep learning face detection model - // ------------------------------------------------ - net := gocv.ReadNetFromCaffe(dnnProtoPath, dnnModelPath) - if net.Empty() { - log.Fatalf("Error reading network model from: %s and %s", dnnProtoPath, dnnModelPath) - } - defer net.Close() - - // ------------------------------------------------ - // 5. Prepare a Mat to store frames - // ------------------------------------------------ img := gocv.NewMat() defer img.Close() - // previousDetections will hold the bounding boxes from the last frame. - var previousDetections []image.Rectangle - aggregatedCount := 0 startTime := time.Now() - log.Printf("Starting DNN-based face detection. Capturing one frame every %v seconds.", frameInterval) - log.Printf("Aggregating new face counts over %v intervals.", aggregationInterval) + var previousDetections []image.Rectangle + log.Printf("[Device %d] Starting detection loop on %s\n", cfg.DeviceID, cfg.StreamURL) - // ------------------------------------------------ - // 6. Main loop: Capture, detect, display, and aggregate new faces - // ------------------------------------------------ for { if ok := cap.Read(&img); !ok { - log.Println("Cannot read frame from camera. Exiting.") + log.Printf("[Device %d] Cannot read frame, stopping.\n", cfg.DeviceID) break } if img.Empty() { @@ -123,29 +146,25 @@ func main() { continue } - // Create a blob from the image for DNN input (resize to 300x300, subtract mean) + // Convert frame to a 300x300 blob blob := gocv.BlobFromImage(img, 1.0, image.Pt(300, 300), gocv.NewScalar(104.0, 177.0, 123.0, 0), false, false) net.SetInput(blob, "") detections := net.Forward("") blob.Close() - // Reshape the detections to a 2D matrix where each row is one detection. totalDetections := int(detections.Total() / 7) detectionMat := detections.Reshape(1, totalDetections) - // currentDetections holds all bounding boxes detected in the current frame. - var currentDetections []image.Rectangle + currentDetections := make([]image.Rectangle, 0, totalDetections) newFaces := 0 - // Loop over each detection for i := 0; i < totalDetections; i++ { confidence := detectionMat.GetFloatAt(i, 2) - if confidence < float32(dnnConfidenceThreshold) { + if confidence < dnnConfidenceThreshold { continue } - - // Compute bounding box coordinates + // bounding box left := int(detectionMat.GetFloatAt(i, 3) * float32(img.Cols())) top := int(detectionMat.GetFloatAt(i, 4) * float32(img.Rows())) right := int(detectionMat.GetFloatAt(i, 5) * float32(img.Cols())) @@ -153,10 +172,10 @@ func main() { rect := image.Rect(left, top, right, bottom) currentDetections = append(currentDetections, rect) - // Check against previous detections to see if it's a duplicate. + // check for duplication duplicate := false - for _, prev := range previousDetections { - if IoU(rect, prev) >= duplicateThreshold { + for _, prevRect := range previousDetections { + if iou(rect, prevRect) >= float64(duplicateThreshold) { duplicate = true break } @@ -165,40 +184,79 @@ func main() { newFaces++ } - // Draw the detection box and confidence label. + // draw bounding box gocv.Rectangle(&img, rect, color.RGBA{0, 255, 0, 0}, 2) label := fmt.Sprintf("%.2f", confidence) size := gocv.GetTextSize(label, gocv.FontHersheySimplex, 0.5, 1) pt := image.Pt(left, top-2) - gocv.Rectangle(&img, image.Rect(pt.X, pt.Y-size.Y, pt.X+size.X, pt.Y), color.RGBA{0, 255, 0, 0}, -1) - gocv.PutText(&img, label, pt, gocv.FontHersheySimplex, 0.5, color.RGBA{0, 0, 0, 0}, 1) + gocv.Rectangle(&img, image.Rect(pt.X, pt.Y-size.Y, pt.X+size.X, pt.Y), + color.RGBA{0, 255, 0, 0}, -1) + gocv.PutText(&img, label, pt, gocv.FontHersheySimplex, 0.5, + color.RGBA{0, 0, 0, 0}, 1) } - // Update aggregated count with only new faces from this frame. aggregatedCount += newFaces - log.Printf("Detected %d new face(s) in this frame (total new faces: %d).", newFaces, aggregatedCount) - - // Update previous detections to current ones for next frame comparison. previousDetections = currentDetections + log.Printf("[Device %d] Detected %d new face(s) this frame, total so far: %d\n", cfg.DeviceID, newFaces, aggregatedCount) - // Display the frame with detections + // Show frame window.IMShow(img) if window.WaitKey(1) >= 0 { break } - // Check if the aggregation interval has passed + // Time to push data? if time.Since(startTime) >= aggregationInterval { - log.Printf("Aggregation interval reached. Aggregated new face count = %d", aggregatedCount) - err = sendDataToThingSpeak(thingspeakURL, thingspeakWriteKey, aggregatedCount) - if err != nil { - log.Println("Error sending data to ThingSpeak:", err) + log.Printf("[Device %d] Aggregation interval reached. Count=%d\n", cfg.DeviceID, aggregatedCount) + + // 1) Send to ThingSpeak + errTS := sendDataToThingSpeak( + "https://api.thingspeak.com/update", + cfg.TSWriteKey, + aggregatedCount, + ) + if errTS != nil { + log.Printf("[Device %d] Error pushing to ThingSpeak: %v\n", cfg.DeviceID, errTS) + } else { + log.Printf("[Device %d] Successfully sent to ThingSpeak.\n", cfg.DeviceID) + } + + // 2) Send on-chain with signature + timestamp := uint64(time.Now().Unix()) + + // convert the deviceโ€™s firmwareHash and signature from hex to raw bytes + fwBytes, errFh := hex.DecodeString(strings.TrimPrefix(firmwareHash, "0x")) + sigBytes, errSig := hex.DecodeString(strings.TrimPrefix(firmwareSig, "0x")) + if errFh != nil || errSig != nil { + log.Printf("[Device %d] Invalid hex firmwareHash or signature: %v, %v\n", cfg.DeviceID, errFh, errSig) } else { - log.Println("Data successfully sent to ThingSpeak.") + // Ensure 32 bytes for fwHash + if len(fwBytes) == 32 && len(sigBytes) == 65 { + var fwArr [32]byte + copy(fwArr[:], fwBytes) + + tx, errTx := perfOracle.UpdateMetricsWithSig( + transactor, + big.NewInt(int64(cfg.DeviceID)), + big.NewInt(int64(timestamp)), + big.NewInt(int64(aggregatedCount)), + big.NewInt(0), // taps=0 for example + fwArr, + sigBytes, + ) + if errTx != nil { + log.Printf("[Device %d] updateMetricsWithSig error: %v\n", cfg.DeviceID, errTx) + } else { + log.Printf("[Device %d] updateMetricsWithSig TX: %s\n", cfg.DeviceID, tx.Hash().Hex()) + } + } else { + log.Printf("[Device %d] FirmwareHash or signature has incorrect length!\n", cfg.DeviceID) + } } + + // Reset aggregatedCount = 0 startTime = time.Now() - // Optionally, clear previous detections here to reset the tracking. previousDetections = nil } @@ -206,13 +264,101 @@ func main() { } } -// getEnv returns the environment variable value if it exists, -// otherwise it returns the fallback value. -func getEnv(key, fallback string) string { - if val, ok := os.LookupEnv(key); ok { - return val +// --------------------------------------------------------------------- +// D. Support Functions +// --------------------------------------------------------------------- + +// readDeviceConfigs reads environment variables for device1, device2, etc. +func readDeviceConfigs() []DeviceConfig { + dev1URL := getEnv("ESP32_STREAM_URL1", "") + dev1IDStr := getEnv("ESP32_STREAM_ID1", "0") + dev1TSKey := getEnv("THINGSPEAK_WRITE_KEY1", "") + + dev2URL := getEnv("ESP32_STREAM_URL2", "") + dev2IDStr := getEnv("ESP32_STREAM_ID2", "0") + dev2TSKey := getEnv("THINGSPEAK_WRITE_KEY2", "") + + d1ID, _ := strconv.ParseUint(dev1IDStr, 10, 64) + d2ID, _ := strconv.ParseUint(dev2IDStr, 10, 64) + + devices := []DeviceConfig{ + { + StreamURL: dev1URL, + DeviceID: d1ID, + TSWriteKey: dev1TSKey, + }, + { + StreamURL: dev2URL, + DeviceID: d2ID, + TSWriteKey: dev2TSKey, + }, } - return fallback + return devices +} + +// setupEthereum sets up an ethclient, parses the private key, creates a transactor, +// and binds to the PerformanceOracle contract. +func setupEthereum() (*bind.TransactOpts, *oracle.PerformanceOracle) { + rpcURL := getEnv("RPC_URL", "") + contractAddrStr := getEnv("CONTRACT_ADDRESS", "") + privKeyHex := getEnv("ADMIN_PRIVATE_KEY", "") + chainIDStr := getEnv("CHAIN_ID", "5") + + chainID, err := strconv.ParseInt(chainIDStr, 10, 64) + if err != nil { + log.Fatalf("Invalid CHAIN_ID: %v", err) + } + + client, err := ethclient.Dial(rpcURL) + if err != nil { + log.Fatalf("Error creating ETH client: %v", err) + } + + privateKey, err := crypto.HexToECDSA(strings.TrimPrefix(privKeyHex, "0x")) + if err != nil { + log.Fatalf("Invalid ADMIN_PRIVATE_KEY: %v", err) + } + + transactor, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(chainID)) + if err != nil { + log.Fatalf("Failed to create keyed transactor: %v", err) + } + + // bind the contract + contractAddress := common.HexToAddress(strings.TrimPrefix(contractAddrStr, "0x")) + perfOracle, err := oracle.NewPerformanceOracle(contractAddress, client) + if err != nil { + log.Fatalf("Failed to bind PerformanceOracle at %s: %v", contractAddrStr, err) + } + + return transactor, perfOracle +} + +// fetchFirmwareHeaders does a quick GET (or HEAD) to the device URL +// to retrieve the X-Firmware-Hash and X-Signature headers. +func fetchFirmwareHeaders(streamURL string) (string, string, error) { + req, err := http.NewRequest("GET", streamURL, nil) + if err != nil { + return "", "", err + } + // We only need headers, but let's do a GET and discard the body + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", "", err + } + defer resp.Body.Close() + + // read up to 1KB just so we fully consume the response + io.CopyN(io.Discard, resp.Body, 1024) + + fwHash := resp.Header.Get("X-Firmware-Hash") + fwSig := resp.Header.Get("X-Signature") + if fwHash == "" || fwSig == "" { + return fwHash, fwSig, fmt.Errorf("missing X-Firmware-Hash or X-Signature") + } + + return fwHash, fwSig, nil } // sendDataToThingSpeak posts the aggregated face count to ThingSpeak. @@ -229,3 +375,24 @@ func sendDataToThingSpeak(tsURL, tsKey string, faceCount int) error { } return nil } + +// iou calculates Intersection over Union of two rectangles +func iou(a, b image.Rectangle) float64 { + intersect := a.Intersect(b) + if intersect.Empty() { + return 0.0 + } + interArea := float64(intersect.Dx() * intersect.Dy()) + areaA := float64(a.Dx() * a.Dy()) + areaB := float64(b.Dx() * b.Dy()) + unionArea := areaA + areaB - interArea + return interArea / unionArea +} + +// getEnv returns the env variable key if present, else fallback +func getEnv(key, fallback string) string { + if val, ok := os.LookupEnv(key); ok { + return val + } + return fallback +} diff --git a/compute-node/oracle/oracle.go b/compute-node/oracle/oracle.go new file mode 100644 index 0000000..5a6ef42 --- /dev/null +++ b/compute-node/oracle/oracle.go @@ -0,0 +1 @@ +package oracle diff --git a/compute-node/oracle/performance_oracle.go b/compute-node/oracle/performance_oracle.go new file mode 100644 index 0000000..2d7204f --- /dev/null +++ b/compute-node/oracle/performance_oracle.go @@ -0,0 +1,33 @@ +// oracle/performance_oracle.go +// ATTENTION: STUB FOR NOW +package oracle + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// PerformanceOracle is a stub for the actual contract binding +type PerformanceOracle struct{} + +// NewPerformanceOracle creates a new instance of PerformanceOracle +func NewPerformanceOracle(address common.Address, backend bind.ContractBackend) (*PerformanceOracle, error) { + return &PerformanceOracle{}, nil +} + +// UpdateMetricsWithSig is a stub for the actual contract method +func (p *PerformanceOracle) UpdateMetricsWithSig( + opts *bind.TransactOpts, + deviceID *big.Int, + timestamp *big.Int, + faceCount *big.Int, + taps *big.Int, + firmwareHash [32]byte, + signature []byte, +) (*types.Transaction, error) { + // This is a stub implementation that would need to be replaced with the actual implementation + return nil, nil +}