(define-module (simulation) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-11) #:use-module (srfi srfi-69) #:use-module (ice-9 atomic) #:use-module (ice-9 match) #:use-module (ice-9 getopt-long) #:use-module (fibers) #:use-module (fibers channels) #:use-module (fibers operations) #:use-module (fibers timers)) (define RX1 1) ; s (has to be between 1-15s) (define RX2 (+ 2 RX1)) ; s (define RECEIVE_DELAY1 3) ; s (define RECEIVE_DELAY2 (+ 2 RECEIVE_DELAY1)) ; s (define RETRANSMISSION_DELAY 2) ; s ;; From RP002 (Regional Parameters): ;; MAC commands exist in the LoRaWAN® specification to change the value of ;; RECEIVE_DELAY1 (using RXTimingSetupReq, RXTimingSetupAns) as well as ;; ADR_ACK_LIMIT and ADR_ACK_DELAY (using ADRParamSetupReq, ADRParamSetupAns). ;; Also, RXTimingSettings are transmitted to the end device along with the ;; JOIN_ACCEPT message in OTAA mode. ;; TODO: put a time limit as a parameter (define-syntax-rule (forever exp ...) (let loop () (begin exp ...) (loop))) ;; Synchronized logger (define *output-channel* (make-channel)) (define (logger) (forever (let ((msg (get-message *output-channel*))) (display msg)))) (define (time->milliseconds time) (let* ((seconds (car time)) (micros (cdr time))) (+ (* 1000 seconds) (round (/ micros 1000))))) (define %start-time (time->milliseconds (gettimeofday))) (define (ll f . data) (spawn-fiber (lambda () (put-message *output-channel* (format #f "~12'0d - ~?~%" (- (time->milliseconds (gettimeofday)) %start-time) f data))))) ;; Synchronized id generator (define *id-channel-in* (make-channel)) (define *id-channel-out* (make-channel)) (define (counter) (let loop ((id 0)) (let ((msg (get-message *id-channel-in*))) (put-message *id-channel-out* id) (loop (1+ id))))) (define (new-id) (put-message *id-channel-in* #t) (get-message *id-channel-out*)) ;; For timer operations (define (seconds->time-unit s) (* internal-time-units-per-second s)) ;; Virtual channel is a combination of radio frequency and data-rate/sf ;; Radio communication that happens in the same radio channel (same freq) but ;; with different spreading factor does not trigger an interference (define-record-type (make-vchannel freq dr) vchannel? (freq vchannel-freq) ; kHz (dr vchannel-dr)) ;; Window channel and DR parameters: ;; These are default values. `RXParamSetupReq` MAC command can be used to ;; change them (define RX2-vchannel-EU863-870 (make-vchannel 869525 0)) (define RX2-vchannel-US902-928 (make-vchannel 923300 8)) (define RX1DROffset 0) (define (upstream-vchannel->RX1-vchannel-EU863-870 vchan RX1DROffset) (define (upstream-dr->downstream-dr dr) (match dr ((? (lambda (x) (< x 8))) (if (> RX1DROffset dr) 0 (- dr RX1DROffset))) ((or 8 10) (if (= RX1DROffset 0) 1 0)) ((or 9 11) (match RX1DROffset (0 2) (1 1) (_ 0))))) (make-vchannel (vchannel-freq vchan) (upstream-dr->downstream-dr (vchannel-dr vchan)))) (define (upstream-vchannel->RX1-vchannel-US903-928 vchan RX1DROffset) (define (upstream-dr->downstream-dr dr) (match dr ((or 0 5) (if (= 3 RX1DROffset) 8 (- 10 RX1DROffset))) ((or 1 2 3) (- (+ 10 dr) RX1DROffset)) (6 (- 11 RX1DROffset)) (4 (if (= 0 RX1DROffset) 13 (- 13 RX1DROffset))))) (make-vchannel (vchannel-freq vchan) (upstream-dr->downstream-dr (vchannel-dr vchan)))) (define (product f l1 l2) (concatenate (map (lambda (x) (map (lambda (y) (f x y)) l2)) l1))) ;; Minimum set of vchannels for EU (define initial-vchannels-EU863-870 (product make-vchannel (list 868100 868300 868500) (iota 6))) ;; All the defined vchannels for the US (define vchannels-US903-928-uplink (append (product make-vchannel (iota 64 902300 200) (iota 4)) (map make-vchannel (iota 8 903000 1600) (iota 8 4 0)))) (define vchannels-US903-928-downlink (product make-vchannel (iota 8 923300 600) (iota (- 13 7) 8))) ;; For End-Device <--> Radio <--> Gateway ;; type can be: ;; '[up/down]link-start ;; '[up/down]link-end ;; 'interference (define-record-type (make-radio-event type id vchannel frame) radio-event? (type radio-event-type) (id radio-event-id) ;; Match the start-interference-end events (vchannel radio-event-vchannel) (frame radio-event-frame)) ;; For Gateway <--> Network Server (define-record-type (make-network-event gateway-id frame) network-event? (gateway-id network-event-gateway-id) (frame network-event-frame)) ;; body can be: ;; 'unconfirmed-data (uplink) ;; 'confirmed-data (uplink) ;; 'ack (downlink) (define-record-type (make-frame FCnt DeviceAddr mac-commands body) frame? (FCnt frame-FCnt) (DeviceAddr frame-DeviceAddr) (mac-commands frame-mac-commands) (body frame-body)) (define-record-type (make-device channel thunk) device? (channel device-channel) (thunk device-thunk)) (define (rand-time) (random 2.)) (define (make-class-a id initial-vchannel upstream-chn downstream-chn) (define window (make-channel)) ;; Activates/deactivates the message sink to avoid blocking on messages we ;; don't need (define internal-com (make-channel)) ;; Make all atomic (define vchannel initial-vchannel) ;; TODO: Unhardcode me (define time-on-air 0.01) (define (process-downlink! frame) ;; TODO (ll "Device ~a got downlink frame ~a" id frame)) (define (send-uplink-frame frame-number device-addr confirmed?) (let* ((event-id id) (frame (make-frame frame-number id '() (if confirmed? 'confirmed-data 'unconfirmed-data)))) (put-message upstream-chn (make-radio-event 'uplink-start event-id vchannel frame)) (sleep time-on-air) (put-message upstream-chn (make-radio-event 'uplink-end event-id vchannel frame)))) (define (receive-window vchannel time) (define (listening-to? vchn) (equal? vchannel vchn)) (define (detect-preamble) (let wait-for-downlink-start () (let ((msg (perform-operation (choice-operation (wrap-operation (sleep-operation time) (lambda _ 'time-is-out)) (get-operation downstream-chn))))) (match msg ;; We got the preamble in time (($ 'downlink-start message-id (? listening-to? chn)) (ll "Device ~a got preamble" id) message-id) ;; No preamble in time ('time-is-out #f) ;; Current message is not a preamble, continue (_ (wait-for-downlink-start)))))) (define (demodulate message-id) (let wait-for-downlink-end ((interference? #f)) (let ((part-of-same-message? (lambda (i) (= message-id i))) (msg (get-message downstream-chn))) (match msg (($ 'interference (= part-of-same-message? id) (= listening-to? chn) frame) (wait-for-downlink-end #t)) ;; Got interference (($ 'downlink-end (= part-of-same-message? id) (= listening-to? chn) frame) (and (not interference?) frame)) (_ (wait-for-downlink-end interference?)))))) (put-message internal-com 'wait) (let* ((preamble-id (detect-preamble)) (result (and preamble-id (demodulate preamble-id)))) (put-message internal-com 'continue) result)) (define (device-operation) (define (wait-until t) (perform-operation (timer-operation t))) (define current-frame 0) (forever (ll "Device ~a waiting for data" id) (sleep (rand-time)) ;; wait for more data (send-uplink-frame current-frame id #t) (set! current-frame (1+ current-frame)) ;; TODO: improve (let* ((now (get-internal-real-time)) (RX1-start (+ now (seconds->time-unit RECEIVE_DELAY1))) (RX2-start (+ now (seconds->time-unit RECEIVE_DELAY2))) (downlink (or (begin (wait-until RX1-start) (receive-window vchannel RX1)) (if (< (get-internal-real-time) RX2-start) (begin (wait-until RX2-start) (receive-window vchannel RX2)) #f)))) (when downlink (process-downlink! downlink))))) (define (downstream-sink) (forever (let ((ev (perform-operation (choice-operation (get-operation internal-com) (get-operation downstream-chn))))) (match ev ('wait (ll "RX window started") (get-message internal-com) (ll "RX window finished")) (_ #f))))) (lambda () (spawn-fiber device-operation) (spawn-fiber downstream-sink))) (define (make-gateway id in radio network) (define time-on-air 0.01) ;s (TODO) (define pending-interferences '()) (define (send-to-device frame) (ll "Trying to downlink") (spawn-fiber (lambda () ;; TODO: choose channel properly (let ((event-id (new-id)) (vchannel (make-vchannel 50000000 0))) ;; TODO: hehe invented! (ll "Gateway ~a sending downlink ~a" id frame) (put-message radio (make-radio-event 'downlink-start event-id vchannel frame)) (sleep time-on-air) (put-message radio (make-radio-event 'downlink-end event-id vchannel frame)))))) (define (send-to-network-server x) (ll "Gateway ~a forwarding ~a" id x) (put-message network (make-network-event id x))) ;; Upstream: listen, and answer in new fibers (lambda () (forever (let* ((ev (get-message in))) (match ev (($ id frame) (send-to-device frame)) (($ 'uplink-start event-id vchannel frame) #f) (($ 'interference event-id vchannel frame) (set! pending-interferences (cons ev pending-interferences))) (($ 'uplink-end event-id vchannel frame) (let-values (((mine not-mine) (partition (lambda (x) (eq? (radio-event-id x) (radio-event-id ev))) pending-interferences))) (set! pending-interferences not-mine) (when (null? mine) ;; TODO (send-to-network-server frame))))))))) (define (make-radio in end-devices gateways) "Fiber for radio resource allocation/control." ;; We can access it only from one fiber! Careful! (define lorawan-vchannels (make-hash-table)) (define (interferences ev chn) (let ((res (hash-table-ref/default lorawan-vchannels chn '()))) (if (equal? res (list ev)) '() res))) (define (use-vchannel! chn start-event) (hash-table-update!/default lorawan-vchannels chn (lambda (event-list) (cons start-event event-list)) '())) (define (release-vchannel! chn end-event) (hash-table-update! lorawan-vchannels chn (lambda (event-list) (remove! (lambda (x) (eq? (radio-event-id x) (radio-event-id end-event))) event-list)))) (define (radio-event->interference ev) "Make a new radio-event of type 'interference from another radio-event" (match ev (($ type id vchannel frame) (make-radio-event 'interference id vchannel frame)))) (lambda () (forever (let ((ev (get-message in))) (match ev (($ 'uplink-start event-id vchannel frame) (ll "Device ~a started uplink-frame #~a on freq ~a DR ~a" (frame-DeviceAddr frame) (frame-FCnt frame) (vchannel-freq vchannel) (vchannel-dr vchannel)) (use-vchannel! vchannel ev) (let ((ints (interferences ev vchannel))) (hash-table-walk gateways (lambda (k gateway) (spawn-fiber (lambda () (put-message (device-channel gateway) ev) (for-each (lambda (ev) (ll "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") (put-message (device-channel gateway) (radio-event->interference ev))) ints))))))) (($ 'downlink-start event-id vchannel frame) (ll "Device ~a started downlink-frame #~a on freq ~a DR ~a" (frame-DeviceAddr frame) (frame-FCnt frame) (vchannel-freq vchannel) (vchannel-dr vchannel)) (use-vchannel! vchannel ev) (let ((chan (device-channel (hash-table-ref end-devices (frame-DeviceAddr frame)))) (ints (interferences ev vchannel))) (put-message chan ev) (for-each (lambda (ev) (ll "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") (put-message chan (radio-event->interference ev))) ints))) (($ 'uplink-end event-id vchannel frame) (ll "Device ~a ended uplink-frame #~a on freq ~a DR ~a" (frame-DeviceAddr frame) (frame-FCnt frame) (vchannel-freq vchannel) (vchannel-dr vchannel)) (release-vchannel! vchannel ev) (hash-table-walk gateways (lambda (k gateway) (spawn-fiber (lambda () (put-message (device-channel gateway) ev)))))) (($ 'downlink-end event-id vchannel frame) (ll "Device ~a ended downlink-frame #~a on freq ~a DR ~a" (frame-DeviceAddr frame) (frame-FCnt frame) (vchannel-freq vchannel) (vchannel-dr vchannel)) (release-vchannel! vchannel ev) (put-message (device-channel (hash-table-ref end-devices (frame-DeviceAddr frame))) ev))))))) (define (make-network-server upstream gateways end-devices) (lambda () (forever (match (get-message upstream) (($ gateway-id frame) (ll "Network event happend!") ;; TODO answer properly (spawn-fiber (lambda () (sleep RECEIVE_DELAY1) (put-message (device-channel (hash-table-ref gateways gateway-id)) (make-network-event gateway-id (make-frame (frame-FCnt frame) (frame-DeviceAddr frame) #f 'hello)))))))))) (define (run-simulation end-device-count gateway-count) ;; We need synchronized logger and counter running in fibers (spawn-fiber logger) (spawn-fiber counter) (let* ((radio-chn (make-channel)) (end-devices (make-hash-table)) (gateways (make-hash-table)) (network-chn (make-channel))) (for-each (lambda (id) (let ((chn (make-channel))) (hash-table-set! end-devices id (make-device chn (make-class-a id ;; TODO: generalize for the US too? (list-ref initial-vchannels-EU863-870 (modulo id (length initial-vchannels-EU863-870))) radio-chn chn))))) (iota end-device-count gateway-count)) (for-each (lambda (id) (let ((chn (make-channel))) (hash-table-set! gateways id (make-device chn (make-gateway id chn radio-chn network-chn))))) (iota gateway-count 0)) (spawn-fiber (make-network-server network-chn gateways end-devices)) (spawn-fiber (make-radio radio-chn end-devices gateways)) (hash-table-walk end-devices (lambda (_ device) (spawn-fiber (device-thunk device)))) (hash-table-walk gateways (lambda (_ device) (spawn-fiber (device-thunk device)))))) (define (main args) (define help " LoRaWAN interference simulator: USAGE: ~/guile simulation.scm -d END_DEVICE_COUNT -g GATWEWAY_COUNT OPTIONS: ~/-g, --gateways~25tGateway count to add to the simulation ~/-d, --end-devices~25tEnd Device count to add to the simulation ~/-h, --help~25tShow this help~& ") (define option-spec '((gateways (single-char #\g) (value #t)) (end-devices (single-char #\d) (value #t)) (help (single-char #\h) (value #f)))) (let* ((options (getopt-long args option-spec)) (end-devices-op (option-ref options 'end-devices #f)) (gateways-op (option-ref options 'gateways #f))) (when (option-ref options 'help #f) (format #t help) (exit 0)) (unless end-devices-op (format #t "ERROR: No end-device count provided~%") (exit 1)) (unless gateways-op (format #t "ERROR: No gateway count provided~%") (exit 1)) (let* ((end-devices (and end-devices-op (string->number end-devices-op))) (gateways (and gateways-op (string->number gateways-op)))) (unless (integer? end-devices) (format #t "ERROR: -d, --end-devices: expecting integer~%") (exit 1)) (unless (integer? gateways) (format #t "ERROR: -g, --gateways: expecting integer~%") (exit 1)) (run-fibers (lambda () (run-simulation end-devices gateways)) #:drain? #t)))) (main (command-line))