From f10eda9683ddf2b6b46329360ca8cfb04c6ba134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Tue, 28 Mar 2023 11:48:08 -0300 Subject: [PATCH 01/14] feat(mochi): update readme + server hooks --- backend/services/mochi/README.md | 405 +---------------------------- backend/services/mochi/cmd/main.go | 66 ++++- 2 files changed, 57 insertions(+), 414 deletions(-) diff --git a/backend/services/mochi/README.md b/backend/services/mochi/README.md index 01e0aaa..06c5331 100644 --- a/backend/services/mochi/README.md +++ b/backend/services/mochi/README.md @@ -1,404 +1,3 @@ +This broker is an implementation of mochi. I had to fork it to customize CONNACK packet userProperty. -

- -![build status](https://github.com/mochi-co/mqtt/actions/workflows/build.yml/badge.svg) -[![Coverage Status](https://coveralls.io/repos/github/mochi-co/mqtt/badge.svg?branch=master&v2)](https://coveralls.io/github/mochi-co/mqtt?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/mochi-co/mqtt)](https://goreportcard.com/report/github.com/mochi-co/mqtt/v2) -[![Go Reference](https://pkg.go.dev/badge/github.com/mochi-co/mqtt.svg)](https://pkg.go.dev/github.com/mochi-co/mqtt/v2) -[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/mochi-co/mqtt/issues) - -

- -# Mochi MQTT Broker -## The fully compliant, embeddable high-performance Go MQTT v5 (and v3.1.1) broker server -Mochi MQTT is an embeddable [fully compliant](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html) MQTT v5 broker server written in Go, designed for the development of telemetry and internet-of-things projects. The server can be used either as a standalone binary or embedded as a library in your own applications, and has been designed to be as lightweight and fast as possible, with great care taken to ensure the quality and maintainability of the project. - -### What is MQTT? -MQTT stands for [MQ Telemetry Transport](https://en.wikipedia.org/wiki/MQTT). It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks ([Learn more](https://mqtt.org/faq)). Mochi MQTT fully implements version 5.0.0 of the MQTT protocol. - -## What's new in Version 2.0.0? -Version 2.0.0 takes all the great things we loved about Mochi MQTT v1.0.0, learns from the mistakes, and improves on the things we wished we'd had. It's a total from-scratch rewrite, designed to fully implement MQTT v5 as a first-class feature. - -Don't forget to use the new v2 import paths: -```go -import "github.com/mochi-co/mqtt/v2" -``` - -- Full MQTTv5 Feature Compliance, compatibility for MQTT v3.1.1 and v3.0.0: - - User and MQTTv5 Packet Properties - - Topic Aliases - - Shared Subscriptions - - Subscription Options and Subscription Identifiers - - Message Expiry - - Client Session Expiry - - Send and Receive QoS Flow Control Quotas - - Server-side Disconnect and Auth Packets - - Will Delay Intervals - - Plus all the original MQTT features of Mochi MQTT v1, such as Full QoS(0,1,2), $SYS topics, retained messages, etc. -- Developer-centric: - - Most core broker code is now exported and accessible, for total developer control. - - Full featured and flexible Hook-based interfacing system to provide easy 'plugin' development. - - Direct Packet Injection using special inline client, or masquerade as existing clients. -- Performant and Stable: - - Our classic trie-based Topic-Subscription model. - - Client-specific write buffers to avoid issues with slow-reading or irregular client behaviour. - - Passes all [Paho Interoperability Tests](https://github.com/eclipse/paho.mqtt.testing/tree/master/interoperability) for MQTT v5 and MQTT v3. - - Over a thousand carefully considered unit test scenarios. -- TCP, Websocket (including SSL/TLS), and $SYS Dashboard listeners. -- Built-in Redis, Badger, and Bolt Persistence using Hooks (but you can also make your own). -- Built-in Rule-based Authentication and ACL Ledger using Hooks (also make your own). - -> There is no upgrade path from v1.0.0. Please review the documentation and this readme to get a sense of the changes required (e.g. the v1 events system, auth, and persistence have all been replaced with the new hooks system). - -### Compatibility Notes -Because of the overlap between the v5 specification and previous versions of mqtt, the server can accept both v5 and v3 clients, but note that in cases where both v5 an v3 clients are connected, properties and features provided for v5 clients will be downgraded for v3 clients (such as user properties). - -Support for MQTT v3.0.0 and v3.1.1 is considered hybrid-compatibility. Where not specifically restricted in the v3 specification, more modern and safety-first v5 behaviours are used instead - such as expiry for inflight and retained messages, and clients - and quality-of-service flow control limits. - -## Roadmap -- Please [open an issue](https://github.com/mochi-co/mqtt/issues) to request new features or event hooks! -- Cluster support. -- Enhanced Metrics support. -- File-based server configuration (supporting docker). - -## Quick Start -### Running the Broker with Go -Mochi MQTT can be used as a standalone broker. Simply checkout this repository and run the [cmd/main.go](cmd/main.go) entrypoint in the [cmd](cmd) folder which will expose tcp (:1883), websocket (:1882), and dashboard (:8080) listeners. - -``` -cd cmd -go build -o mqtt && ./mqtt -``` - -### Using Docker -A simple Dockerfile is provided for running the [cmd/main.go](cmd/main.go) Websocket, TCP, and Stats server: - -```sh -docker build -t mochi:latest . -docker run -p 1883:1883 -p 1882:1882 -p 8080:8080 mochi:latest -``` - -## Developing with Mochi MQTT -### Importing as a package -Importing Mochi MQTT as a package requires just a few lines of code to get started. -``` go -import ( - "log" - - "github.com/mochi-co/mqtt/v2" - "github.com/mochi-co/mqtt/v2/hooks/auth" - "github.com/mochi-co/mqtt/v2/listeners" -) - -func main() { - // Create the new MQTT Server. - server := mqtt.New(nil) - - // Allow all connections. - _ = server.AddHook(new(auth.AllowHook), nil) - - // Create a TCP listener on a standard port. - tcp := listeners.NewTCP("t1", ":1883", nil) - err := server.AddListener(tcp) - if err != nil { - log.Fatal(err) - } - - err = server.Serve() - if err != nil { - log.Fatal(err) - } -} -``` - -Examples of running the broker with various configurations can be found in the [examples](examples) folder. - -#### Network Listeners -The server comes with a variety of pre-packaged network listeners which allow the broker to accept connections on different protocols. The current listeners are: - -| Listener | Usage | -| --- | --- | -| listeners.NewTCP | A TCP listener | -| listeners.NewUnixSock | A Unix Socket listener | -| listeners.NewNet | A net.Listener listener | -| listeners.NewWebsocket | A Websocket listener | -| listeners.NewHTTPStats | An HTTP $SYS info dashboard | - -> Use the `listeners.Listener` interface to develop new listeners. If you do, please let us know! - -A `*listeners.Config` may be passed to configure TLS. - -Examples of usage can be found in the [examples](examples) folder or [cmd/main.go](cmd/main.go). - -### Server Options and Capabilities -A number of configurable options are available which can be used to alter the behaviour or restrict access to certain features in the server. - -```go -server := mqtt.New(&mqtt.Options{ - Capabilities: mqtt.Capabilities{ - ClientNetWriteBufferSize: 4096, - ClientNetReadBufferSize: 4096, - MaximumSessionExpiryInterval: 3600, - Compatibilities: mqtt.Compatibilities{ - ObscureNotAuthorized: true, - }, - }, - SysTopicResendInterval: 10, -}) -``` - -Review the mqtt.Options, mqtt.Capabilities, and mqtt.Compatibilities structs for a comprehensive list of options. `ClientNetWriteBufferSize` and `ClientNetReadBufferSize` can be configured to adjust memory usage per client, based on your needs. - - -## Event Hooks -A universal event hooks system allows developers to hook into various parts of the server and client life cycle to add and modify functionality of the broker. These universal hooks are used to provide everything from authentication, persistent storage, to debugging tools. - -Hooks are stackable - you can add multiple hooks to a server, and they will be run in the order they were added. Some hooks modify values, and these modified values will be passed to the subsequent hooks before being returned to the runtime code. - -| Type | Import | Info | -| -- | -- | -- | -| Access Control | [mochi-co/mqtt/hooks/auth . AllowHook](hooks/auth/allow_all.go) | Allow access to all connecting clients and read/write to all topics. | -| Access Control | [mochi-co/mqtt/hooks/auth . Auth](hooks/auth/auth.go) | Rule-based access control ledger. | -| Persistence | [mochi-co/mqtt/hooks/storage/bolt](hooks/storage/bolt/bolt.go) | Persistent storage using [BoltDB](https://dbdb.io/db/boltdb) (deprecated). | -| Persistence | [mochi-co/mqtt/hooks/storage/badger](hooks/storage/badger/badger.go) | Persistent storage using [BadgerDB](https://github.com/dgraph-io/badger). | -| Persistence | [mochi-co/mqtt/hooks/storage/redis](hooks/storage/redis/redis.go) | Persistent storage using [Redis](https://redis.io). | -| Debugging | [mochi-co/mqtt/hooks/debug](hooks/debug/debug.go) | Additional debugging output to visualise packet flow. | - -Many of the internal server functions are now exposed to developers, so you can make your own Hooks by using the above as examples. If you do, please [Open an issue](https://github.com/mochi-co/mqtt/issues) and let everyone know! - -### Access Control -#### Allow Hook -By default, Mochi MQTT uses a DENY-ALL access control rule. To allow connections, this must overwritten using an Access Control hook. The simplest of these hooks is the `auth.AllowAll` hook, which provides ALLOW-ALL rules to all connections, subscriptions, and publishing. It's also the simplest hook to use: - -```go -server := mqtt.New(nil) -_ = server.AddHook(new(auth.AllowHook), nil) -``` - -> Don't do this if you are exposing your server to the internet or untrusted networks - it should really be used for development, testing, and debugging only. - -#### Auth Ledger -The Auth Ledger hook provides a sophisticated mechanism for defining access rules in a struct format. Auth ledger rules come in two forms: Auth rules (connection), and ACL rules (publish subscribe). - -Auth rules have 4 optional criteria and an assertion flag: -| Criteria | Usage | -| -- | -- | -| Client | client id of the connecting client | -| Username | username of the connecting client | -| Password | password of the connecting client | -| Remote | the remote address or ip of the client | -| Allow | true (allow this user) or false (deny this user) | - -ACL rules have 3 optional criteria and an filter match: -| Criteria | Usage | -| -- | -- | -| Client | client id of the connecting client | -| Username | username of the connecting client | -| Remote | the remote address or ip of the client | -| Filters | an array of filters to match | - -Rules are processed in index order (0,1,2,3), returning on the first matching rule. See [hooks/auth/ledger.go](hooks/auth/ledger.go) to review the structs. - -```go -server := mqtt.New(nil) -err := server.AddHook(new(auth.Hook), &auth.Options{ - Ledger: &auth.Ledger{ - Auth: auth.AuthRules{ // Auth disallows all by default - {Username: "peach", Password: "password1", Allow: true}, - {Username: "melon", Password: "password2", Allow: true}, - {Remote: "127.0.0.1:*", Allow: true}, - {Remote: "localhost:*", Allow: true}, - }, - ACL: auth.ACLRules{ // ACL allows all by default - {Remote: "127.0.0.1:*"}, // local superuser allow all - { - // user melon can read and write to their own topic - Username: "melon", Filters: auth.Filters{ - "melon/#": auth.ReadWrite, - "updates/#": auth.WriteOnly, // can write to updates, but can't read updates from others - }, - }, - { - // Otherwise, no clients have publishing permissions - Filters: auth.Filters{ - "#": auth.ReadOnly, - "updates/#": auth.Deny, - }, - }, - }, - } -}) -``` - -The ledger can also be stored as JSON or YAML and loaded using the Data field: -```go -err = server.AddHook(new(auth.Hook), &auth.Options{ - Data: data, // build ledger from byte slice: yaml or json -}) -``` -See [examples/auth/encoded/main.go](examples/auth/encoded/main.go) for more information. - -### Persistent Storage -#### Redis -A basic Redis storage hook is available which provides persistence for the broker. It can be added to the server in the same fashion as any other hook, with several options. It uses github.com/go-redis/redis/v8 under the hook, and is completely configurable through the Options value. -```go -err := server.AddHook(new(redis.Hook), &redis.Options{ - Options: &rv8.Options{ - Addr: "localhost:6379", // default redis address - Password: "", // your password - DB: 0, // your redis db - }, -}) -if err != nil { - log.Fatal(err) -} -``` -For more information on how the redis hook works, or how to use it, see the [examples/persistence/redis/main.go](examples/persistence/redis/main.go) or [hooks/storage/redis](hooks/storage/redis) code. - -#### Badger DB -There's also a BadgerDB storage hook if you prefer file based storage. It can be added and configured in much the same way as the other hooks (with somewhat less options). -```go -err := server.AddHook(new(badger.Hook), &badger.Options{ - Path: badgerPath, -}) -if err != nil { - log.Fatal(err) -} -``` -For more information on how the badger hook works, or how to use it, see the [examples/persistence/badger/main.go](examples/persistence/badger/main.go) or [hooks/storage/badger](hooks/storage/badger) code. - -There is also a BoltDB hook which has been deprecated in favour of Badger, but if you need it, check [examples/persistence/bolt/main.go](examples/persistence/bolt/main.go). - - - -## Developing with Event Hooks -Many hooks are available for interacting with the broker and client lifecycle. -The function signatures for all the hooks and `mqtt.Hook` interface can be found in [hooks.go](hooks.go). - -> The most flexible event hooks are OnPacketRead, OnPacketEncode, and OnPacketSent - these hooks be used to control and modify all incoming and outgoing packets. - -| Function | Usage | -| -------------------------- | -- | -| OnStarted | Called when the server has successfully started.| -| OnStopped | Called when the server has successfully stopped. | -| OnConnectAuthenticate | Called when a user attempts to authenticate with the server. An implementation of this method MUST be used to allow or deny access to the server (see hooks/auth/allow_all or basic). It can be used in custom hooks to check connecting users against an existing user database. Returns true if allowed. | -| OnACLCheck | Called when a user attempts to publish or subscribe to a topic filter. As above. | -| OnSysInfoTick | Called when the $SYS topic values are published out. | -| OnConnect | Called when a new client connects | -| OnSessionEstablished | Called when a new client successfully establishes a session (after OnConnect) | -| OnDisconnect | Called when a client is disconnected for any reason. | -| OnAuthPacket | Called when an auth packet is received. It is intended to allow developers to create their own mqtt v5 Auth Packet handling mechanisms. Allows packet modification. | -| OnPacketRead | Called when a packet is received from a client. Allows packet modification. | -| OnPacketEncode | Called immediately before a packet is encoded to be sent to a client. Allows packet modification. | -| OnPacketSent | Called when a packet has been sent to a client. | -| OnPacketProcessed | Called when a packet has been received and successfully handled by the broker. | -| OnSubscribe | Called when a client subscribes to one or more filters. Allows packet modification. | -| OnSubscribed | Called when a client successfully subscribes to one or more filters. | -| OnSelectSubscribers | Called when subscribers have been collected for a topic, but before shared subscription subscribers have been selected. Allows receipient modification.| -| OnUnsubscribe | Called when a client unsubscribes from one or more filters. Allows packet modification. | -| OnUnsubscribed | Called when a client successfully unsubscribes from one or more filters. | -| OnPublish | Called when a client publishes a message. Allows packet modification. | -| OnPublished | Called when a client has published a message to subscribers. | -| OnPublishDropped | Called when a message to a client is dropped before delivery, such as if the client is taking too long to respond. | -| OnRetainMessage | Called then a published message is retained. | -| OnQosPublish | Called when a publish packet with Qos >= 1 is issued to a subscriber. | -| OnQosComplete | Called when the Qos flow for a message has been completed. | -| OnQosDropped | Called when an inflight message expires before completion. | -| OnWill | Called when a client disconnects and intends to issue a will message. Allows packet modification. | -| OnWillSent | Called when an LWT message has been issued from a disconnecting client. | -| OnClientExpired | Called when a client session has expired and should be deleted. | -| OnRetainedExpired | Called when a retained message has expired and should be deleted. | -| StoredClients | Returns clients, eg. from a persistent store. | -| StoredSubscriptions | Returns client subscriptions, eg. from a persistent store. | -| StoredInflightMessages | Returns inflight messages, eg. from a persistent store. | -| StoredRetainedMessages | Returns retained messages, eg. from a persistent store. | -| StoredSysInfo | Returns stored system info values, eg. from a persistent store. | - -If you are building a persistent storage hook, see the existing persistent hooks for inspiration and patterns. If you are building an auth hook, you will need `OnACLCheck` and `OnConnectAuthenticate`. - - -### Direct Publish -To publish basic message to a topic from within the embedding application, you can use the `server.Publish(topic string, payload []byte, retain bool, qos byte) error` method. - -```go -err := server.Publish("direct/publish", []byte("packet scheduled message"), false, 0) -``` -> The Qos byte in this case is only used to set the upper qos limit available for subscribers, as per MQTT v5 spec. - -### Packet Injection -If you want more control, or want to set specific MQTT v5 properties and other values you can create your own publish packets from a client of your choice. This method allows you to inject MQTT packets (no just publish) directly into the runtime as though they had been received by a specific client. Most of the time you'll want to use the special client flag `inline=true`, as it has unique privileges: it bypasses all ACL and topic validation checks, meaning it can even publish to $SYS topics. - -Packet injection can be used for any MQTT packet, including ping requests, subscriptions, etc. And because the Clients structs and methods are now exported, you can even inject packets on behalf of a connected client (if you have a very custom requirements). - -```go -cl := server.NewClient(nil, "local", "inline", true) -server.InjectPacket(cl, packets.Packet{ - FixedHeader: packets.FixedHeader{ - Type: packets.Publish, - }, - TopicName: "direct/publish", - Payload: []byte("scheduled message"), -}) -``` - -> MQTT packets still need to be correctly formed, so refer our [the test packets catalogue](packets/tpackets.go) and [MQTTv5 Specification](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html) for inspiration. - -See the [hooks example](examples/hooks/main.go) to see this feature in action. - - - -### Testing -#### Unit Tests -Mochi MQTT tests over a thousand scenarios with thoughtfully hand written unit tests to ensure each function does exactly what we expect. You can run the tests using go: -``` -go run --cover ./... -``` - -#### Paho Interoperability Test -You can check the broker against the [Paho Interoperability Test](https://github.com/eclipse/paho.mqtt.testing/tree/master/interoperability) by starting the broker using `examples/paho/main.go`, and then running the mqtt v5 and v3 tests with `python3 client_test5.py` from the _interoperability_ folder. - -> Note that there are currently a number of outstanding issues regarding false negatives in the paho suite, and as such, certain compatibility modes are enabled in the `paho/main.go` example. - - -## Performance Benchmarks -Mochi MQTT performance is comparable with popular brokers such as Mosquitto, EMQX, and others. - -Performance benchmarks were tested using [MQTT-Stresser](https://github.com/inovex/mqtt-stresser) on a Apple Macbook Air M2, using `cmd/main.go` default settings. Taking into account bursts of high and low throughput, the median scores are the most useful. Higher is better. - -> The values presented in the benchmark are not representative of true messages per second throughput. They rely on an unusual calculation by mqtt-stresser, but are usable as they are consistent across all brokers. -> Benchmarks are provided as a general performance expectation guideline only. - -`mqtt-stresser -broker tcp://localhost:1883 -num-clients=2 -num-messages=10000` -| Broker | publish fastest | median | slowest | receive fastest | median | slowest | -| -- | -- | -- | -- | -- | -- | -- | -| Mochi v2.2.0 | 127,216 | 125,748 | 124,279 | 319,250 | 309,327 | 299,405 | -| Mosquitto v2.0.15 | 155,920 | 155,919 | 155,918 | 185,485 | 185,097 | 184,709 | -| EMQX v5.0.11 | 156,945 | 156,257 | 155,568 | 17,918 | 17,783 | 17,649 | - -`mqtt-stresser -broker tcp://localhost:1883 -num-clients=10 -num-messages=10000` -| Broker | publish fastest | median | slowest | receive fastest | median | slowest | -| -- | -- | -- | -- | -- | -- | -- | -| Mochi v2.2.0 | 45,615 | 30,129 | 21,138 | 232,717 | 86,323 | 50,402 | -| Mosquitto v2.0.15 | 42,729 | 38,633 | 29,879 | 23,241 | 19,714 | 18,806 | -| EMQX v5.0.11 | 21,553 | 17,418 | 14,356 | 4,257 | 3,980 | 3,756 | - -Million Message Challenge (hit the server with 1 million messages immediately): - -`mqtt-stresser -broker tcp://localhost:1883 -num-clients=100 -num-messages=10000` -| Broker | publish fastest | median | slowest | receive fastest | median | slowest | -| -- | -- | -- | -- | -- | -- | -- | -| Mochi v2.2.0 | 51,044 | 4,682 | 2,345 | 72,634 | 7,645 | 2,464 | -| Mosquitto v2.0.15 | 3,826 | 3,395 | 3,032 | 1,200 | 1,150 | 1,118 | -| EMQX v5.0.11 | 4,086 | 2,432 | 2,274 | 434 | 333 | 311 | - -> Not sure what's going on with EMQX here, perhaps the docker out-of-the-box settings are not optimal, so take it with a pinch of salt as we know for a fact it's a solid piece of software. - -## Stargazers over time 🥰 -[![Stargazers over time](https://starchart.cc/mochi-co/mqtt.svg)](https://starchart.cc/mochi-co/mqtt) -Are you using Mochi MQTT in a project? [Let us know!](https://github.com/mochi-co/mqtt/issues) - -## Contributions -Contributions and feedback are both welcomed and encouraged! [Open an issue](https://github.com/mochi-co/mqtt/issues) to report a bug, ask a question, or make a feature request. - - - +![img.png](img.png) \ No newline at end of file diff --git a/backend/services/mochi/cmd/main.go b/backend/services/mochi/cmd/main.go index 7593739..da327a7 100644 --- a/backend/services/mochi/cmd/main.go +++ b/backend/services/mochi/cmd/main.go @@ -5,7 +5,9 @@ package main import ( + "bytes" "flag" + "github.com/mochi-co/mqtt/v2/packets" "github.com/rs/zerolog" "log" "os" @@ -17,6 +19,18 @@ import ( "github.com/mochi-co/mqtt/v2/listeners" ) +var server = mqtt.New(&mqtt.Options{ + //Capabilities: &mqtt.Capabilities{ + // ServerKeepAlive: 10000, + // ReceiveMaximum: math.MaxUint16, + // MaximumMessageExpiryInterval: math.MaxUint32, + // MaximumSessionExpiryInterval: math.MaxUint32, // maximum number of seconds to keep disconnected sessions + // MaximumClientWritesPending: 65536, + // MaximumPacketSize: 0, + // MaximumQos: 2, + //}, +}) + func main() { tcpAddr := flag.String("tcp", ":1883", "network address for TCP listener") wsAddr := flag.String("ws", "", "network address for Websocket listener") @@ -32,17 +46,6 @@ func main() { done <- true }() - server := mqtt.New(&mqtt.Options{ - //Capabilities: &mqtt.Capabilities{ - // ServerKeepAlive: 10000, - // ReceiveMaximum: math.MaxUint16, - // MaximumMessageExpiryInterval: math.MaxUint32, - // MaximumSessionExpiryInterval: math.MaxUint32, // maximum number of seconds to keep disconnected sessions - // MaximumClientWritesPending: 65536, - // MaximumPacketSize: 0, - // MaximumQos: 2, - //}, - }) l := server.Log.Level(zerolog.DebugLevel) server.Log = &l @@ -89,6 +92,11 @@ func main() { } } + err := server.AddHook(new(MyHook), map[string]any{}) + if err != nil { + log.Fatal(err) + } + go func() { err := server.Serve() if err != nil { @@ -100,4 +108,40 @@ func main() { server.Log.Warn().Msg("caught signal, stopping...") server.Close() server.Log.Info().Msg("main.go finished") + +} + +type MyHook struct { + mqtt.HookBase +} + +func (h *MyHook) ID() string { + return "events-controller" +} + +func (h *MyHook) Provides(b byte) bool { + return bytes.Contains([]byte{ + mqtt.OnConnect, + }, []byte{b}) +} + +func (h *MyHook) Init(config any) error { + h.Log.Info().Msg("initialised") + return nil +} + +func (h *MyHook) OnConnect(cl *mqtt.Client, pk packets.Packet) { + log.Println("new connection") + var clUser string + if len(cl.Properties.Props.User) > 0 { + clUser = cl.Properties.Props.User[0].Val + } + if clUser != "" { + log.Println("new device:", clUser) + err := server.Publish("oktopus/devices", []byte(clUser), false, 1) + if err != nil { + log.Println("server publish error: ", err) + } + } + } From f816e19f754662d511e0fd01b9e8a084fdccf366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Tue, 28 Mar 2023 11:49:10 -0300 Subject: [PATCH 02/14] feat(controller): send publish to IoT and watch new connections --- .../services/controller/cmd/oktopus/main.go | 28 ++-- .../controller/internal/mqtt/mqtt-client.go | 142 +++++++++++++++--- .../services/controller/internal/mtp/mtp.go | 2 +- backend/services/mochi/img.png | Bin 0 -> 42553 bytes 4 files changed, 133 insertions(+), 39 deletions(-) create mode 100644 backend/services/mochi/img.png diff --git a/backend/services/controller/cmd/oktopus/main.go b/backend/services/controller/cmd/oktopus/main.go index 3978781..f56aa37 100755 --- a/backend/services/controller/cmd/oktopus/main.go +++ b/backend/services/controller/cmd/oktopus/main.go @@ -25,10 +25,9 @@ func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println("Starting Oktopus Project TR-369 Controller Version:", VERSION) - //flBroker := flag.Bool("m", false, "Defines if mosquitto container must run or not") // fl_endpointId := flag.String("endpoint_id", "proto::oktopus-controller", "Defines the enpoint id the Agent must trust on.") - flSubTopic := flag.String("s", "oktopus/+/agent/+", "That's the topic agent must publish to, and the controller keeps on listening.") - // fl_pub_topic := flag.String("pub_topic", "oktopus/v1/controller", "That's the topic controller must publish to, and the agent keeps on listening.") + flDevicesTopic := flag.String("d", "oktopus/devices", "That's the topic mqtt broker end new devices info.") + flSubTopic := flag.String("sub", "oktopus/+/controller/+", "That's the topic agent must publish to, and the controller keeps on listening.") flBrokerAddr := flag.String("a", "localhost", "Mqtt broker adrress") flBrokerPort := flag.String("p", "1883", "Mqtt broker port") flTlsCert := flag.String("ca", "", "TLS ca certificate") @@ -44,10 +43,6 @@ func main() { flag.Usage() os.Exit(0) } - //if *flBroker { - // log.Println("Starting Mqtt Broker") - // mqtt.StartMqttBroker() - //} /* This context suppress our needs, but we can use a more sofisticate approach with cancel and timeout options passing it through paho mqtt functions. @@ -58,15 +53,16 @@ func main() { If you want to use another message protocol just make it implement Broker interface. */ mqttClient := mqtt.Mqtt{ - Addr: *flBrokerAddr, - Port: *flBrokerPort, - Id: *flBrokerClientId, - User: *flBrokerUsername, - Passwd: *flBrokerPassword, - Ctx: ctx, - QoS: *flBrokerQos, - SubTopic: *flSubTopic, - CA: *flTlsCert, + Addr: *flBrokerAddr, + Port: *flBrokerPort, + Id: *flBrokerClientId, + User: *flBrokerUsername, + Passwd: *flBrokerPassword, + Ctx: ctx, + QoS: *flBrokerQos, + SubTopic: *flSubTopic, + DevicesTopic: *flDevicesTopic, + CA: *flTlsCert, } mtp.MtpService(&mqttClient, done) diff --git a/backend/services/controller/internal/mqtt/mqtt-client.go b/backend/services/controller/internal/mqtt/mqtt-client.go index 0a49591..9df597f 100644 --- a/backend/services/controller/internal/mqtt/mqtt-client.go +++ b/backend/services/controller/internal/mqtt/mqtt-client.go @@ -4,25 +4,31 @@ import ( "context" "crypto/tls" "crypto/x509" + usp_msg "github.com/leandrofars/oktopus/internal/usp_message" + "github.com/leandrofars/oktopus/internal/usp_record" + "google.golang.org/protobuf/proto" "io/ioutil" "log" "net" + "strings" "sync" + "time" "github.com/eclipse/paho.golang/paho" "github.com/leandrofars/oktopus/internal/utils" ) type Mqtt struct { - Addr string - Port string - Id string - User string - Passwd string - Ctx context.Context - QoS int - SubTopic string - CA string + Addr string + Port string + Id string + User string + Passwd string + Ctx context.Context + QoS int + SubTopic string + DevicesTopic string + CA string } var c *paho.Client @@ -30,15 +36,20 @@ var c *paho.Client /* ------------------- Implementations of broker interface ------------------ */ func (m *Mqtt) Connect() { - msgChan := make(chan *paho.Publish) - go messageHandler(msgChan) - clientConfig := startClient(m.Addr, m.Port, m.CA, m.Ctx, msgChan) + devices := make(chan *paho.Publish) + controller := make(chan *paho.Publish) + go m.messageHandler(devices, controller) + clientConfig := m.startClient(devices, controller) connParameters := startConnection(m.Id, m.User, m.Passwd) conn, err := clientConfig.Connect(m.Ctx, &connParameters) if err != nil { log.Println(err) } + if conn.ReasonCode != 0 { + log.Fatalf("Failed to connect to %s : %d - %s", m.Addr+m.Port, conn.ReasonCode, conn.Properties.ReasonString) + } + // Sets global client to be used by other mqtt functions c = clientConfig @@ -60,24 +71,48 @@ func (m *Mqtt) Disconnect() { func (m *Mqtt) Subscribe() { if _, err := c.Subscribe(m.Ctx, &paho.Subscribe{ Subscriptions: map[string]paho.SubscribeOptions{ - m.SubTopic: {QoS: byte(m.QoS), NoLocal: true}, + m.SubTopic: {QoS: byte(m.QoS), NoLocal: true}, + m.DevicesTopic: {QoS: byte(m.QoS), NoLocal: true}, }, }); err != nil { log.Fatalln(err) } log.Printf("Subscribed to %s", m.SubTopic) + log.Printf("Subscribed to %s", m.DevicesTopic) +} + +func (m *Mqtt) Publish(msg []byte, topic, respTopic string) { + if _, err := c.Publish(context.Background(), &paho.Publish{ + Topic: topic, + QoS: byte(m.QoS), + Retain: false, + Payload: msg, + Properties: &paho.PublishProperties{ + ResponseTopic: respTopic, + }, + }); err != nil { + log.Println("error sending message:", err) + } + + log.Printf("Published to %s", topic) } /* -------------------------------------------------------------------------- */ -func startClient(addr string, port string, tlsCa string, ctx context.Context, msgChan chan *paho.Publish) *paho.Client { - singleHandler := paho.NewSingleHandlerRouter(func(m *paho.Publish) { - msgChan <- m +func (m *Mqtt) startClient(devices, controller chan *paho.Publish) *paho.Client { + singleHandler := paho.NewSingleHandlerRouter(func(p *paho.Publish) { + if p.Topic == m.DevicesTopic { + devices <- p + } else if strings.Contains(p.Topic, "controller") { + controller <- p + } else { + log.Println("No handler for topic: ", p.Topic) + } }) - if tlsCa != "" { - conn := connWithTls(tlsCa, addr+":"+port, ctx) + if m.CA != "" { + conn := connWithTls(m.CA, m.Addr+":"+m.Port, m.Ctx) clientConfig := paho.ClientConfig{ Conn: conn, Router: singleHandler, @@ -91,7 +126,7 @@ func startClient(addr string, port string, tlsCa string, ctx context.Context, ms return paho.NewClient(clientConfig) } - conn, err := net.Dial("tcp", addr+":"+port) + conn, err := net.Dial("tcp", m.Addr+":"+m.Port) if err != nil { log.Println(err) } @@ -179,8 +214,71 @@ func startConnection(id, user, pass string) paho.Connect { return connParameters } -func messageHandler(msg chan *paho.Publish) { - for m := range msg { - log.Println("Received message:", string(m.Payload)) +func (m *Mqtt) messageHandler(devices, controller chan *paho.Publish) { + for { + select { + case d := <-devices: + payload := string(d.Payload) + log.Println("New device: ", payload) + m.handleNewDevice(payload) + case c := <-controller: + m.handleDevicesResponse(c.Payload) + } } } + +func (m *Mqtt) handleNewDevice(deviceMac string) { + payload := usp_msg.Msg{ + Header: &usp_msg.Header{ + MsgId: "uniqueIdentifierForThismessage", + MsgType: usp_msg.Header_GET, + }, + Body: &usp_msg.Body{ + MsgBody: &usp_msg.Body_Request{ + Request: &usp_msg.Request{ + ReqType: &usp_msg.Request_Get{ + Get: &usp_msg.Get{ + ParamPaths: []string{"Device.DeviceInfo."}, + MaxDepth: 1, + }, + }, + }, + }, + }, + } + teste, _ := proto.Marshal(&payload) + record := usp_record.Record{ + Version: "0.1", + ToId: deviceMac, + FromId: "leleco", + PayloadSecurity: usp_record.Record_PLAINTEXT, + RecordType: &usp_record.Record_NoSessionContext{ + NoSessionContext: &usp_record.NoSessionContextRecord{ + Payload: teste, + }, + }, + } + + tr369Message, err := proto.Marshal(&record) + if err != nil { + log.Fatalln("Failed to encode address book:", err) + } + time.Sleep(5 * time.Second) + m.Publish(tr369Message, "oktopus/v1/agent/"+deviceMac, "oktopus/v1/controller/"+deviceMac) +} + +func (m *Mqtt) handleDevicesResponse(p []byte) { + var record usp_record.Record + var message usp_msg.Msg + + err := proto.Unmarshal(p, &record) + if err != nil { + log.Fatal(err) + } + err = proto.Unmarshal(record.GetNoSessionContext().Payload, &message) + if err != nil { + log.Fatal(err) + } + + log.Printf("Received a usp_message: %s\n", message.String()) +} diff --git a/backend/services/controller/internal/mtp/mtp.go b/backend/services/controller/internal/mtp/mtp.go index d910f31..5ba3583 100644 --- a/backend/services/controller/internal/mtp/mtp.go +++ b/backend/services/controller/internal/mtp/mtp.go @@ -13,7 +13,7 @@ import ( type Broker interface { Connect() Disconnect() - // Publish() + Publish(msg []byte, topic, respTopic string) Subscribe() } diff --git a/backend/services/mochi/img.png b/backend/services/mochi/img.png new file mode 100644 index 0000000000000000000000000000000000000000..1e2f50a0e02264f3a6ecbc6311f6b3161fddcc16 GIT binary patch literal 42553 zcmb4qby!s0*S3n%DLphuND2&HIy54UNDBxK-Cd%Tz<`K^0@58rcc+B3Gz=j*bPPib z^Nr8*d*1lo-*;W_cm6up-gB-qd!4=4y4Skz^-)VhnFt?%f9K8}B2^UyojZ51Ab0NE zeTa*V88N(m5`O2-yF021&-8rE_LeS;DGb&{k8Y&q?l}}(@V}s-q)0SV_~yZ@t-#BL zFWLhzb^r$e5h?k7j{rHUXM&1Wz@(nnzS&BF?H5wjQUpU(He?XeLnc{j>a=vP&Aq+G zi(%SjKjYO0tbbo`m#ffNWPe@pl=5tU-!=A(sVMw)@e2YU{yq7e5c2oX@6YPsDK>F` zmg3XTnOp^Y)3C0#J*M0ku4lib63E2Cqt_)lV?^DN1MXTf-VgGB+ViPJKw>fF) zT(M${O~a^xB9;-XD3i;~*VKSSjz=#A`m1co$!vgKLtozANR*7F zQ9sD*uKGYKzYb@w$vApXrI(o7t?3*Z!9tu75z^|#kQX*b__ZcvvKc*#1XipP#_lhrxk;h7GKhwZs`^;5t=gs4W}@eB0Us+9e-@^;Kb+hWP_)*6Z0UGm z9uDH6H_vhja8I(DPYdH3q$6PRR{oZ_s#4@EZ{whOeT@I2zc4rQd%Q!Sw2s<0En!Xi zk0$TBO0}O9=WT4nYL+iTPzKv;pG>AAcR4Ce=THQQjiDIOZ~9J6+Z&Dv#G@HsH}?18Bzt<{;QT>6oO5NbB?B<*u04 zJRI95GkWLCv&AAc)q5;n5n5N|>3c*i^-YcK9>>VD^s%DJ=q>HpAca*+J&Kr6Gsm5k z*kW77&L2;oqj*F%;|b#mh4k8mX*<757KXwSx$U=I1%OS9ZH>B3^od=FT1%8~xYZj^ zI*vHJI=q2sEmI&iHc1xB2g&B(z%Hb-$gm^K3aqE4R0Js-v+XsL-qC^2Yf1P09Cx$6 zuk^yPw}*!MoNvnfcW!TCO*!n-JHgtn+qfiz`2q6x;0D56GFZQNeWgJ7X<1zFk3n`W zCZ@-U7sS@Z9rP4pqOTR+MXy(K?wjkca&7mZbR$@8)$B*F%+GI$Y61u2jm-{j20s@u zNw}SJx-tO5gc7ZRI%!5)&P;%fU4!z#!C!w)Hj&|oe2yfQtuPfE+w|{On;MPJjY2hz z-XrD`K9SPnLLyAxf+!)Xd_&r@rEeeg%el+G`~|6!PS*zvq)HApnI0o1*#-RRESV5mRb$#|Dr_;d$h zlsdLe`{CvR&v**$f#uSO3R_^~@lyRdH|Rv$$Q<05s9u;MIBE2WalATkJ!P(t2QY3= z$L}oXE(~m1ZnJ*G##vc#dygY(h|@zrpd4B!L}GD!_lV|b-xC2(W}ycBJg>h`p6$0g z`bCuPT#+sBl*c{5ZG(6(*?X~Vo|Bj?>+VD{u}qfwDp{De=kq7F>M7BlQY`!CW*4`3 z`)vB+ZI(Nz)fM(b*wclAR1^NgH)#KKSWtpgyH}b+y8%k|3Lo-v*M~DpyF6&gA%h4EnLVU)gb8T^ABfe9bB5!MRDp9gDp@6-F teuu`)Vxml+oGKwXbSx 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 ^MD!Dcd5p{_P7F5w{heA058@LiOf)RT1Hv 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 k|9 Date: Wed, 29 Mar 2023 11:52:55 +0000 Subject: [PATCH 03/14] feat: side nav icons + theme color palette --- frontend/public/assets/devias-kit-pro.png | Bin 104848 -> 0 bytes frontend/src/components/logo.js | 2 +- frontend/src/layouts/dashboard/side-nav.js | 16 ++-------------- frontend/src/theme/colors.js | 8 ++++---- 4 files changed, 7 insertions(+), 19 deletions(-) delete mode 100644 frontend/public/assets/devias-kit-pro.png diff --git a/frontend/public/assets/devias-kit-pro.png b/frontend/public/assets/devias-kit-pro.png deleted file mode 100644 index 4a3bc347bc2c1b8c0081634d1cea35d5d4e7843f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104848 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%0DYF!P>&7q3VD^$snek%o-sLXLwQ9yDRoT9 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

0(!|xYCgY{;UMn%@AKvxACkSUWI zxZD&y%OsoDru@0z@Y=B6KoMo#N`i7*)XBNWtdOvp>B@$8X1YG%?AcaRyxLhG_9E`c zE1=GR&h0IG_&3J;biC!vo3C_6Z0_P?GMlbV+TJ2;6Wtl4$rPy)-OL&LJGK$V;|WFCaRp_18%1m-n8Mg?qA2rZ|qK$A>juji42L7*5dHk_=ssXmV?k@Q~S8Ju{aE*g%L|0j`Fd(aNEOMO<^N6bCl|4WmEt zZ?KND77Vd5zKwWD+|{tmq$Q)XS2CM@Bx9;2yiw73od!h}2R zru@t(w4`3#+~JImn|$ zB3WjAv3Y7_@5gtnVfLCeG!I|J?4|qAS@VSXrL!=X7tVm|#by|3vB}4scH=?Nt&=ee zXMV{lI0_F7i&pTgSX;;Zoj-}~gZIOH`d?TuG4T6|Ap(p8a^um^onav7WwhnI0%iOy@r3r}L? z+c$*u1s5%_e(+O(DNHT8EYda+aEko9$kXH-&#g*VQ{$vV&pIccS{ zKGPmmp3*m6Z~^u(nsT$UMuxfs-#ac=Uthw8Ei-g(N+vWj86GE;zMJ ztVA{w1+lO(!qxxuD8}!5n=IrBWXI)qgY|?x}RdI2gXVd~t1H_|sIXI7o?C&?t zzY}-P?)Pg(Y#1JWeCy&b;g{)u#2w8&_`>4T2t}}UkJkJ0G$mJPX?U zt;y(H-lE!Bh0&UwPAl^}D9|E74=EEU^B++qjdQ2$X!V!jCt3j6N`9wd$~GhWy>{P* z8A>uO*9n1O%4!AhgClzqd>s64vM+5vGMReCa#XB_ZUgCYT4|FjIgKiByfj!Kud~=5 zT@`oJR_N^r{k`pR(8rG$l~nW)P!7A2(=BC(gr>>M zYKn4DAw*i4HZdOf~D(P5G9I~s+&O%!}?9e@S;=E3j7`Ir7xpFf|0 z!9ESEdOEO>M9J6c!kB{ReEpknLSq+t(SNb>V|ee*ui)hT&YVB&b;wwO*sbD@?p}NW zpZ2B#u47p`uR6Z##JcXFI%;_Twl^Yf6`@T_P+>K=l~*=6lel`phpa;gv}GMVW@1e} zCxKW0m>*?K*ZnLdS(h;h{tUMlRRDZ(UXub9(MIDzLJEvDy~!MzS6Kyp`{v}bgPMLH z*8Q{;;HD51awBG(xHGU88bH+4UEe{{+D)GvU+$29(*_Bs+#pgfy-VIV==)Sm@v9!A zxlGkj#1KLz^rC-E_c0X0NYN+lM{12noOOj1rYuvrt3|JA$yrKzwJ>bL;&W^Yp)k-V zdYvrGg;ZfHfZw|+0W=LUh-NBNkEbiSjHknEU`H(J|Hl^$i*?j)`N3Ch3#nqqxb~M%rp0@w+qYVoCkBM)BGcqzqcH!q< z{rKiTuzr^rZr}eR`|W!o5OItT?fx!K)AM-G=oeBa<5udZGMa6omM^o~!fP+w?B9n! z3cPUMPnG=EZ%oreBadokRR9F2&QE#!=gjS=@Me=1LO4zT0XwpI)wd%9cH#G!WdZnQ zn6iJD2UDpW2Sz|gyI1S`x@=BrFH&t0f;HnwlP(Pa@+5QKEst|w*Zrs3-61!)uOWc;3=0@hEeaQ5dmD@5M$4-J0(KR01?gt$?q zTZd?ThZ2)Q91$4fro|!vG|xWTdD}khzGgMHL;KKG(7mhZ*8FxNcwo)lFoqN$L_-76aNysm(N%eNt4wf#R~D;-GiI444RG!@p3lv-WJC*=8& z&t%|H)SLc{YD?o+ty2wTq+U$HGUesI`^5@gG<&4xox3vdS7{44w3W1=z@zuR;K>?D z#2;a5Qwd_yZ&p^FhKfr%Uf@ZM1MYj5Mh}ZHt(T{;yLBtOeyehKzvI_K@NfLv^;25o z{jT4ZW;;A{q6cjsdf6+1`(NUxy;57y3*XLUHRoom1c(~7)y~@y^PLpLbVc147#Z7> zkO@>dtKFD>eUgeJb*t-CvmQ}i?|S4B37|eJlEM|rNsoWKi|bei!X~UK@&rxElqCl` z^&!n~-`O?T-koD?Mc2x-NgN+*E)<5)7g9a$MyBP_&^Mqbq)2BuAS|SDx}pQ<2JIGp zo}kGjPpDHvaTq8LHPx0mp#vRRFjnp6rd1p?S0McFGU?FE8k3v#CiVr}i zZ2~IznX2R+(?X1W**?01>2F_zaXXF7L0{g&_`SBSuDJGKCtTCk$BocjtL4P~Q$)$n(h#uFhu8x9SB#0DAW> zeADlCWk+PAMjU}YDYwwj38^Y^9FDI7UJbXk>8AJr*=Yq4N4es zX4`zIUjgdOd9M0pocx0R74b-f3n2pwM|&7e$r_f}m!%C%E-d0k;U@8o^J`6!nWu*M zH1Y>3*cg7Nm9h*9kP#H4P+stAt+-M!19f=x=?i%J)D>L35|*-CO>dta^WS{KI_|vb z0M^%ojl7^zk3{r4EdXML*Dlq7C5@!SsS8##aK=cL#y3(xqcVy=s#W-@K~`^7Nn)UU zE9jj?(IJ_yHcle0IpXhbg|Pj&63$v!J4(26Rnaf?6mFAH^wVJH4(w&Rs@HkDk8GoP z{RS5IPu+2p-Sf8hzWWGfH;kP5X7eJ5Q zJvsT(psnrV%CG+yFx#iBla>_L%I0?zgoMz_l#a3yoe6dKG5RArING$@136Kn2!{iMH{Vmb-w}XZswEUR} zgPjEQ@)t)2@?Iuxn4|=mT3^!O9zI5)Ksk*@$5CPBU9VbHewq0TUMPL=4+uwXe|qst z{p_#oqOirws%krFYp6E0O=ATMccZKJ{ac4msvB_dYBZzj&qBIvQ5ciOm1UCc3}HiQz?Ht()!JxCY{r%9!HW@BMdHt^Y$zY}#!@0hB2=wwoi5VA=_0EI+4IK%U~1 z$IH0rHwd&JpBQ}&qwXmew~?OFAP6Gk&U|R+6FZt|v%QVj9Ki?uX!C_^2&j2Y^3Qw< zA)6xyful#l78+1IKuP%McTGelCNKy!8vD97F}r*^cMu%BXz%g`;Q8k*Xl!}2kC<;9 zh%mSWQ;?W>WIB_sB zU=e}DLgb@=Y)9_*T0JQU2MNP%apW+5@xlzeAMSbEM&~WtU2HmBZO2YnT((4#^b23dn_hk!4jmk&eQOH9zH()Tt5Baqc7kvww?NP+weJF*asCa?i=?N<&xB)(+ps#@XK|X+ zI+(`KR=V$2;Im%_E?y4cZgSq2I;NrnGW~p@+IPRrZ$UBAW-Z<3CI?DqqG-JEAQrAH zK$;b+9XF8)L^vC9GFy;2ciNiOe#4)G&4Qrp@kYfpimA1UHX9w7;#bjIf{oNcDHN)g z$?L!cl|0ml2`KuVNy&Ek>$)J(aDzNS2`ftQ6OY;YI}eM7A2CeV2a+&@g0)(6+L6$;3lKMq6FVbv zAeM~92D-Iv@X3cySq*5R7%FFr^kl4-)ZvSdpT%oma@$IKGisEU*z|=S(X<6%RSrg`keyzJ-UmdyKNVO&3_nSOJ4 z;Py*9xDD80&At@lrOhM%+VvFf5RI6P8j9q(vX9p+O%vBvAaR?IG1)$d`^Im;$J^iV z0*+Ek$WVb)LWERirg0qy@NJtvjMZk9*Lnw&oZs$&5BND`g}}S45ct%?{$Qzij&c>C(Gr-rM^%`bZ2!kHLmY_NW_85={l6DR06=PR{9g4S2FUoS73^Kp#P#-eI2VEDX zkaQqz|DLgRcf;}2K0^FtGQss;v`2OKJ}7$Qh!P&?xzF5xueFKY)H?r>BTddgA&N7s z%$l{F*hLSPiE;wTteL?TCHLj`A!@jVK6X@kv-3I`T8LI!WI?FDw9VkB*@uI45D%JH;RM})RVxMJhJHaSlYnK(yLtGqxj~bN%l|lR=Qn%625*9B`^Tc( zE+%vsIRwoprNSxRJH<>i?WfvGev=1Z4ZQMI$kvUOLa!vB@8{Pu3yX!}$e^bgMACdv?!sT5YIW*3uBXY&F67Z6{55%SW0y6TQitvfJes-EEBmFA4 z_EQMcWtFEpmk};$l2#kl7ynGbmR}PLx|3!joD@Bl$?{>^HYUoY(7s*=@9D!*BXb8^2{{SU_h3z2G;9VgcOFJ&_Ij!rU3% zK``6D%^RmMJbKfnGEU15UC$qREe;-iBihq{q-+ENsuU}}3BQJ9vqm1-jInd#n{fH1 ze+QH4iZkzsCNs4{;lOyGrE7$5yZl%1w%MP@uG@i+oPZGl*I1%PBXT|t&b+mn+0?3C zD^|FzjBP5mJgkf~$aNjJRY&6m!JmDp|Ck0|>V8x++m@@7L}076bb zY1$THEoS5+<^$f6@=UVuf)IS?|0sM6gM38JGI3QQFOTSK9KC+J3tb4mnj+Q z-d#;*bJhvQPA!^>l06Re!~Aqfv5_!=(Zwb&0HtZv&npN06h6JvryC*I=WX7+?*0iz zAKmiWz3iLdt}YG~o3JO4#BD-7@VnXVQ*8d3b!)n5v@Er>Ti~+*JzqV9QxE=qtUd8d z7+reQZ4Ag8A}yLVJ3?8pqZfPMT8BL!dDpJHXJZ8et!$v#e*?z*k72sDW*vfKw=y^i zM&~QB0>I|8F`C-8-Eaplc?|Xy>BGuek#O%!n5`W}`H^b2nVbU1COFOG)-T94Kvk8o z>7&5VGqVWVQdqr2$KtfdVGH!YlU&U^R^IWTZzSNu;TR|$ewZA9EYun;>HnZU;Ofz| zW|$ggXrUu&LH{L1K|yd5&!Bh<~cW?0a5s0)~`qGd0; z*#CfC7&N_NMw|rtIK#^E3+I4;`cJ$+W_PB*zbi0E`H6Xov2&EW(XMfaMy(y}aDWv< zE6JRHWW`!3R>+%JZ5Uqk%E#E+E`^gP$>Jb-%8hPkZ7j;#zLL!3Z{Y-q z36&&1ipbL8U;kcqW|a`*G4(o{OVd{5Lyw%r`HNdlNG!xT&O(9#BZzy+ft08hKSi8o z>gZ}+Z#{lPr%CH+N+b5h1wMZjU9RHTIRa&mi<`YvW)iCD{fk?v8pp4t!b@4 z_RpH3Y`Bg`7yQTDyIMif@gl!fax2oi`=^*5AZHV$!udU!@)T4n3`V!$>iU0*#cbwB zHV_|2<23Cic+K6YhE0>!gUteHY>l_=c;bP+rEHsYp1aTWGPY*Sj!it4Xg`(M*M!u2 z2}%^uz6ryESXTx+DrB5J{RDp`vetXYnK^%Nys%`vn0OhMXjc9_R3rJ@3>tFLLX$FTey>) zm(*bW3Nzc2QfH8v#%tx_c0Ifw`19|w=HPA5K(ZlX8~R{NO*T6*p5qb6*T{|u2t~|h zxj8xlKes_}xfOxeSjwWluU&xHj8}#k}MZ<~E(4E(mOsV1+9nol}XalFDm15o; zN10Zq%iQB)Drh1P#th0+D+G?c40zR#+2QRYei2ug)#(JrNQ6cBlMV5BCJ;Muc`D!9 zdT{1WlA~A3!GJ$*!mkb69cbeUR*ed!tzT~i9amj9=uf>xFatZ^3;B^v*9J3@DXSnx zl(vi7KdC<`CMLv1mt+M~%ZFlh;dGsD?zUxdLdYGCrSc05>DE?Qd(n)ip}1LLMUUV% zWZO8O1Ha>^%f_?8`IpTMYRhzYjO?yIA(A`gu?K9u9`o%LVJw`H72zsa*KNf^5HcijgwAwr3csLWN; z(GM|Qm4T^+)zK=%Qs_(9eFYu490`>;DBVE%4ENasw!iXniEMu_tvJg2P)-iV>wR{_ zU$UK?=i(^e+)fKT-J-+bI}tz18@>_vzVAm=(xh*hO8aN+bv**aooBP?+cQgBD$hW< z>BIp%_rhg6#NJIc8Yt4jg96Y_>{#lKn+`jm>s{n`4Ly9IaZYPv>P#sDB=WOK?@_$# zJu7f8J5>ZJLeHedURKf^z<{JA0x95A{j4XDNae+U?&P;|`ei|I>^ART(&bVviD+|| znPkzCJIc=jAf0y;@HylaM_EhiSm!P7o=i|Hpf4W%ZzFjGeh~HDr0EAn772z3Aw>Ms zBQbZ_jz#RTMe*^@6^!N{>06#6V*D;#*b*sZU#H<3)dJo;y%!mT$Q)=QAuKk-+{Vz*~P5IPswOYt**1 z$?92(e6UjY0my_0yCN@%pZNt8-_kW8`yjZg;M&q7&&%+jcvMPbk%t+07NODWn5b7| zcxQk*6QeFn3>DxW(%8OE9|$VsE;CN`RTpZ=WNxm(A zVXK$9toZNx!Z40l!otF7ID2Jc&0)um5e^?s6=Hs<=@WF2F|XfTbXn~-_fj6 z6BFgW@ z=Ba;c+5{=Vk)w2-)JUV`rSn&1&%z@b_}plKtP1Z&0iBnKBzsnXht-|;A%#rbRA4(q z>a0y~QH)L-p5|z6IpgiiIPm0GF#7mMFh6q!=IW*$5p3HLoAqYe9%rnoHMc#jvuavr zpF8WJT+BPas{h6tG5wo=3(ZSk$DT#V!g&@BJGK))^T)u4|F!MVJ3dbn|EbrHu7k_g zDf-D@wvLOt-K^X=TFUAUJ#aR+SJH06nMRIXC<~u1)4#nIAHuTBl=qZ_$aN%@(JN_~ zCE;V&Fi8+*eI=x)`)ZALLQa1z)67ypDIA}La=lkv9j=JT`i9Ow=j3ilcc<9gpJM!3%6Ng7`p^M%_DAuv*7fct#1u@7qYg9_F8U#hy$b5us z5>hEVG(-5>TCu<|@{_(5m_k6A|Jr&d$;b-goHR|raqENur#C|8kEuz|UePp7x`RkL zROicH69zZsi^+vBamNi)r-kRSw>Fcp9R-Z-=wLM8wx*_;9Szt~0=yvM5klN1F-(MS zj9-tku*kc+au8SSpRx7bg#x&?;X|pcinjK-&tm(Z{9~Ltb3T~hOr}Y>CLsl?fk-zx zdEzNdfBgRm`l+8rXYGL9$gj6xt?sJzJAL&dRukMQc#cpRjiPK0cb$L6Yl_Z9I6^i* zuM8}8{sbzQ9ND6}k?e9XKtg(10ail)T}-3b!Rm@zO(0}xl$lXKzhze`y!2=)_RClo zIT0E^Vz8Qk4Rp17hN22IpVzB(=_PY$Dh*>wEHY_jJqw0!f49N&&vov8Wwo(_z&L05 ziknG==+kA(gg?Z~JIb@8YTGo8->*}m;8XODb-i{eo{z2=lPGV#S`)Tx?cTvNWzm-| zBt(%&%t2k^!&tQyaPzS-4s4vnne#i=0ltHsnV;Ufy3*pv!By)t8ex5!_6drn5k+)% zwvK9)tbG(?l|qH6VE9@Y)QiYyrUKMP2~2Jbo>n?0?E>l95!*%=CQB-RP%TDbordnr zRCWZD?z0YCDc4{R29+(7*dtoIZWt^;`ClX*%P4IHRlB6Y1BR7|c&SkLJ-wtPkjz6;Q3) z!jSFCmOSiiB_7iIPbJ4>U_7X{Dio7Nhiq5d=mROHdWtUzXZuYh{H2u|+RHW)BcrE5 zO1_Z1RoGtOA>dbiwd{*~@}*Aj8jDf-6i||?g@;PlD0=k^U6y_O(K9(aJ$H7k)cGq2 zgL%xaqzQR>bh}%98gG0ZS^tE$SGHk#rT6QGou&mV8?i1>&O55!1rJG;Dc%$E6AB4^ z{|xH9;vmD|b4RCm1jV&Y|0et=+lO>Ty3X0nL&tT9;Bf2m?ERK~y!5}c_vO)cUFCh>KKI-~ zPwz>3vSeA3!IC5689TOvnLG)SHc*E^zzk^@Nyti@l+d(UO`CtHR@>DDSu0tql%{P~ z-85lX)KJ=xLLhC*P)uwL9%^HQEhAeNmZkUZdv|*Gy*r$<^L=|d-`?k34PXTv@6W!v z@19}L`}=*v-rvUW4*u@rXMk$uEy(a%JP%iPS)P;}sN4WF`Q{ARJk2OvD-PfeQnQN; ze6h=X(|feI4f(cOWjE4}VG4;dUDxMplOKT2)TWJVwEKO-Tq74(YY_#7*8*(buUCzU zl;x#XElr^@?7*h4d;~gQ{uny$hHArGC@~6GAam%H1p;c!6Q6+A6g9oS^FEL`mQ>_m zTE2|}iCjaVS<&F3Q;^JSLK=|t+FOB!%LmMaLrn)gi~58`aI2iAQ&v7%9y9dWdD##a zc2D5RFK#PN@-yX@!LyF>bMOe}QI?k(77eixx5S&Y!b$ISeg$K$0*?|&_S_k;PC4s0 z2NKT6E6OEil<`y?~7QI?8#{7wYNOc%FTw`Kh-m+?|iR(xeCuV~;ZY_@`yTC|6A zXA5?AJ_nxZ-GKx6*Ggl6)yL~US+1L4AOa%j16d6wO!%RNns=dQQ>x``C%P$*w`#+`4x_F9sr&G~o{s98+0)x( z;T-0Zf)CL7KSw+NzeZc?e*c(M(mn*DP5tRdpz{|WfdOe4Zer*LcRRX47s>*s3qoo7 zYVz=xKpy-Rkj)HDhN)uqYEQ}76xI~SI^~sVOlmMm%z(;Gr%D@g#mqp;izYsq+AO2j zZ7fv)M?Pe13@R0zGZS$B6$19ZM!(G*J2KpM#CC_JIXX1 z`Xvelg$fV}$uc7Qql%Hr8P_Wl;owZ1>pASLQo%*gB3t(VLjM0T+rrbeQB7xtpf*Z zQL(zv0*#q89?uOcEZ84rem-`dfZvA-nhhXXvHDW53PM$89>f;cyiNqDlyXin_A7~l zgajYNsvORjn}{Tsgi?jSV>+M`cUMM5pAOE1AGe| zvK^HC*CrYq#^!sSmg7zJXCqUmgt?F}G$3!>1@Ie}<0PSdA;t!8!9y+oLxg@EO=1x#~kYTA;*0Fx84Z(7ye9XAk#$c zi*?|kfC~LEdIgIK%&F4$`^-wzwA+1(s#%uPs=!>S=424gp{aM@Lor5NbDk;lO+aj` zy2Tl#0M7>V_fhP8|2V@axAlZ14HAVxs`35{ybrUzG1vy%%C5e>cp#WbUfy3#$pf z{Cd5iE@2z)6rVGj?aqqoY6#uEk;5;~S@7cg$PQCr{bkC;s}lBu$2Y)dv7si)7hBl- zd=7g;IRlcarVP6Shx*~^5Z)o?MZe;r zOqmHvnPbdeAE-?sbyN+K4E;emFs0hF6HIC!g;^hR^926x1dk{amVlB2mKYu_#|-Dp zDW;hBoU4}z7%Q7U9^$~@`7p^+U07PBmw?qRXeeT&7rq3A ztk>w(OEy5JeV=}3!wH-XeR(Iq4aeb=s3?D6TL!x-rGHjL_jfQ>to6n{n6ohi#r!*M z`V2f|6`x>y(^!^isCAJ)EaUWRWAZ(0rvVP}9`dAz@;ZFtrSoD1X3!~CVNFGvrY>V% z5cjtzGv2VmC8o`oe+AE1l{)pK?D2wO3`sGlLLFCpv>(3+D-82v8G7}O-^Ijc@jW!?ki{`QKN%p=c zG1`1Y@vLe?6KK#n+l9)5&Kjq92eN1cV)D5LkQ!FBv2_RJ2j2zqf{S5F>dT||g81S` z)FD&E(sScTRDKTy!m=c@5YQCG)OpnB=V0RxKL%?H+o6WsL2g-4N5-WmPr~>^zYp@A zZv!~`v|F8}r8Mc%RBa@|J04Z-yV$e=2j7*$++q{bRtDQILNnT?9RATqa}_lgYGe4? zCmZm>i>4j)D|mDgj8)@5+#D=3<;>sf(<%~v|9V55&xe5F+v}m7 zymU{VI`1=@=THGK!HZVHmQ{HKyutlM@ZEm^;LUF>&THWe+sreWC-o&b3p#{_`J$9- zYaRIQl@Z*Dn!gCkq`qVU2$JzK!Rq9m7vROe;$UDOQ<1u*8Maep#a_jC+6aOxIMw=e z;xY<$U6d+UlLoXg{mvvqU>HFkr(qY()@?C{_RIDF#k`n^x;WGB@n|N1G(}oHCioya zU6#WC=6u;rw5s>`=!9Yt$(b(%??M?+T)q;j1t@dfbB0+)YMA7iXdoyli>HTrJb0Th zS;bx5^8!H7_6o;n7wH&^_L6MyFhJoewnV@bh(_jo>YTl+Holo)ZIxUm*QOeYhG3l) z0RgE}v<#%?cA)!pE!x>?X~p@~Hgt`CP@*`Kpbukm%e!G?VK+4IyBpI?q5qa&gM}yV z2YLKE3V<}D5v3VJnu0zx+??mG7`#*fqm}_5xB?~@UIdNb`ydv#4wK*br_i|f>mbo9 zDt7OMY~Mb}pMFZM+EQtN^p+xP)0WP``j^N&NHWx5_bLp<9E?y_TY5H!EsGK!y=MgD zwG^hq26P{T?TkWLFGoB;-?TB3@9_>b!<#^$m`?jUMz6UE#4|@A+r3*wY%YWaso>FzS|Rk(Nv?rVS%#e! zeE$7A(FZkE`P}&S30!%bfN$JcM<3HBXrbm*H>(U?0Rc*qaLLDDo~O8oB>+K9RHjZy zb7s8}qVSaPc2x#pUx{_^*`!28uacnu|fY>K>Q4p=cKyX}Xpc z$FNc;pN0VdGS$&O2#QwGn$y2GSx{)2O>|8i4#dS~v*C6Oz}eraIS9<1>B3BiMv7PC zyq1c4@3>T)Wk7dLuS75NH>CXV(Gh(9r5U^_6>uY2ZgF1r1xfreZ#U}+o$J}hVc(lb z7z|fo6}5-u{$QnZ<0ac^?L_{=0D&KbW}C1wwI8K$&_KmtgW2H0r3}ubJ*j#Id1J9+ z6jzla9OadRe%4KMjZ5o1TQ%ABKDQ2>0SQ~G0F+A4DK(|gLZE6hpV_lPp99Ef4qhbS z{%29@*(c%8pA_)Wss<0$wn+i(R{LN|fHOH+0I*AhDev3<8Nd~91{HA7NsWeyE+3Zg z?JwboKazM(zP!{YIzj6Y*DJ_S($CPdaQ*Gjzxk~i>@4$?da~1b^+{s^flTNXR8o5) zauv})m~kUblhPR^dg-t7+pD~hi4WKHJ6FFP>o|q!SH1$`y6XU5@haUOy@6Xkslb8< zb+Sy3dHv`eI;2b60PE^K@`t1emK8q@fz+%I z%%nw3j~M2TT>w{K3vmB^P#XKPDqC5eLM6w$2*x}30uDeK0Bjps$FpVnD$IDQnLm}6 zq<>=iDx8MdRZXFKS|+?k%egwI}e@oV>Fza#7XxZSF;ph$j zuAo`BJ)o;9D-^(H%gPxfN=mZ(+~^pb0dW<-Dm&gbR-#sn+~#lFf^GQw`_};c(zTkO zeV{Aho!2Dr&MVR3e!qkdKMXDuh&sz6_tUmOV9SuRyeE_|zVq$Bl+fvCaKq1~${e7W zKXT`<-$mYc0hj)ifP4Q#F_B$;7=r=^SbAj`Eti$bk)1sLO-MfUTaaIT6=dh{RlE5` zw*&IzGtk19Cc7_$jAGDFdhT#5VfB5RzR>0+LPCSLy3Mdq*@K6f1Iwv7Oc2FE;-e7Or0^{lIjpjPc`pGqy$%p3i~3xdn5ZU#7U5>1{@7h(T}`KQG2ZN**XR}>D@U3RGj z7LZQql4j|ah9#AjQt9qeU}@>*2TMyTDJe)SAfeO}QW8rpDIp-u%kx*f^JzZMy>sTA zi91DjPLX_rPGbxcF1g(zpkI&;54wt;b^tCx?&OQtP~TC(DWFC|)$f-<{P_5u-->_z zahQJUlqA;vgqu%@f_^)`?>}RPxKlrwL8hmZZBik4152 z_`R?IRGyf%vh-PH9_H+A>C(C2(JdKHNLfrc%&LzdE#X|NliLWZIRCjLl&+0E8SKp4de{G5;A%V=ZUvMz zKZJjMz=Fewk?52oLzW$`qDz_?fgYm9$}e8ox>ZWQ{Y)=p$yT1qM#*mJX(#{jVfOx*=?p0X#k-+M?6g+AhEw~2aP#WP6${QfcAf=8&rk^U=jF+=}A zjMn}Qrz)%bU5vzsC;2hMarZOk?f1{5pvC=cZa%Z}bwqtjxVFl)DH~ik7 z>MQpLBczJ@UfJSmS!e&g;AacWX=h@46msEu{ivQKIgOWQ2+8sPh&j&a5y;%g{$Lz) zVyeA1G-J73skFK6aVo~mG;%G?&RrJ|tM>|VAn7zDFYL>8t>J-0{48H*AT&5&r|#3Z z5cA-^yeJD(9+NtzVwitzh~RQ9j^*UjoP3lB`b5z%lc`6k7^&E3 z6xo-2=os9ogGYz^Qn#fCi$DkIjJ2d-Gct6CGaJji5J*ZSwDi7B+gu23i{3Rdxct>J z*ZI~~hb<_A1F1dz)kS>v!oSIl?BD`(J2*RLYC0q-e{(5C9CRAiMD>2h&*a0AQdv_) zEbd%CLaoFn#0){|7Z3yh-4o{XfVuN`h&&mQUQYPPrXrW$IFekKb{J5;f^{>s&bF>#Y`wf$WXlsO zqMIrx)w9eX_wPI-43eITC;8ye*&>oGg2slTc}Oauab&lS<~r_-%W@&SO2BQc-kUBELTH5dxXg)B;-& z(y7Xp)c~#FRz{#d13^|;%zE&;D~}o(2XvJ6ZDLJVr;dSsuc%J6|7!7=^onMZJE;=N z^jb#r^{>`Q71+w_DfHgwH)zehy`3Pw{q1ZkGfdfTv4!y^AqmGR_9H!Hn97^)OcP%b zGQYkHZ==i%pEfcQfk&Wer(0(gD91_Ot% zCbR8YH$`$HDFXxOsgV67D7Z`6K^2*Xm{{Yml5(lJM^D|Tuzi7H$ya_`(D9TD&CBL=`g=J=^}DoherTm3Lmb$|FC*y95(7t#E`~V?_&4YMR?qWpcq?MS z^0NWYhmU2)ZHF7FHL`eD(A!WCI#=UzT)2iIEa*#wy&K@T>8`>kx6-E|S7)jm4`mJrW@ zLvtAoZKO^tQ@(KgM?EDQR_VmT*1zMCdVYh);r0+ZToR+@npXXhmWl8QX!p>-MQJ9Q zY0>_9#9V)9)4P(Q=h%0L65Q2=pE9P;%4nZ`G!0Vi@#!)V?LP`f(a&d}c;nTdV}o6# zYTnSd?qdU`&cp{BVzit+I>g;2w&_NKAkuS3;gACiK2Sjg0cN(o-Q?2i+`DL#OX}*H z&`MHa+4xt%Ne_4LGu!5SuQoW1CQJKf4<&^6>83)zq{O`FF)C`O)rHuflZ>zn6EOr) ziOj8bxlb(Aem*YK1#`HK=**yL_u(yzq?Tb74)KG4i=taw-~&DFeNiYOI;0*a@Xde# z8SmbogI+$p`hroDH#Ae2l@!E~4n7&SM)R&Gw@v)1#gQa(Z^IW@q(5TRUhw-ukHvy} zBd()r6n05US4)bY`pR}+=!)rq>P+f)}lX>uK5^CtUNW;+Dduu;rXz96upG4+^Aj1eyvlr>$X7X`Ae|$0rS8c z)-4Tw!T2yW6WTkIPn0{pn{qBVIvsS8*W$ra3x%Ne1COzbr$Urt-N}!4-=5C>v2r=)8LPsG9Bs*lBtl)QU}!(MiTi%RH6tP|5STDv~s zZPtC!@+wwt%TTF27G*TeaL zK#SzA*PPih6|r#T2jo6 zGcBKYnp15_zX4%Ek?0b)37OBA8zoSh5U*#{H?@bKwU~YisTL7+j*9gpTy4~F&sm2= zZ{HyP_QS*j_Lt;P;>~q47pt(;JiU{El|GUPRrxl+bbAnK9yRlF3?0g7E@J*09^?2Y z!jXFM-8F~MCM$lG@hwI{VJ;=a#Rg0>!+gz9ckGH_)7QB2c}1^5A2LS@esuiHnT}5} zx564E^_6Z#K_RV;e|Er5x2eGiX07Or7x(2hziGu0=Oh06Q7k2bq*~?MTJgrU(rVKh zdBo5)>jcl#nRioKjjh;DGFL-POp&{T-Bj*}>-2eG^~4|e8YAlu=Bb2aUD~7o{>X<; zGh}^O;$ZnEq<*+~n6Wj9 zp|rrjf%d&jljaMJ_e{4icvu_r+Q+|9G6t!Iukz=d6g1PlLHSbOPXwNvrC9C)uRHum zUvY&#<;y{bQedG~|JDBLm7!McftwQ|a%KY_$1JsCj$CyNK#L_F^qdr~Q>;{hS)mP?3mE^h}!P0qrNG<9*9_}ur|e`$Q@*mB0Yj(N{csbcBX}*hv&N{ zV;71?nu?qM&jHn-leRpw`A0-k!EI|-4JZ1(6)+S#bq|Gj+nGu~vk!#TUsR8f=d&x%q?lxMIBwht+&|U!nIU37^Q!YjU35j*$Bv7>gg3-2 zDuvAy_*MpW@wb(P_!_7Vs6m6h>2iYae~2fkmt+r!lDHF)sr}XGj6>K{4Uzr~J!t~2 z_<{k8Az58}Lf!=v#kBjoC0HpG4Bp2aS=kE}!HA=Jv&=~&l zp^uox+8;W+e=k0RK?M8Qz7Vn}D3LE3@3(dT4C2tT^n}^mo6SW|kRsc7@Ic}W{Fvi^p~>Yp-!)05o+&E4InUmG~?&lVt8O_J(iB(&#Sve|@b zmb!bK+7;WQ`1SwbZ@#jlSOCbQmiO_*P`b01rjP#trZh?<80=l*svyUAD}!`3b#BGV zwODe8%jwbm?MnlLFRSw9-QiZgW=r#3&;A^h*s|)IEl*AoZ)UI;mXh} z_4ZTBFvKa9v~wm4a11~K;zb#3Z! zMU`}YzjGMsRnUOlD@Up2&4FiN&942?JALb=3r12ot3=II*Yb#Nove1|0f)TiE3?pRca|rG%Abyxl|Ugxtg6A` z>2Qq+-buB-qq<&jbIDO=ID0jd&^EtW)C(GegDt=2=u)%YRfTxnzZp1@Cpo2CZyIe( zv1xQl^jMbS67RDu0d0q>p0Z$m7@aODoUvz?v<_Z12`{#Wuj z>qQGIn(>hGQ)P6_hB0>BM_qrCgrrg4+(>oU&OpOoZCCB!0CT*IQ!^3Ocli;rKD$(Jie?{28zeI;F^3K)En&v_N@B=|0#T zMxJ5p6+@~Kigm~EPM=Nk&G_SDVb|E^e7WUtoVcFBFc?VGA#XhwHX(K8gT7Lg)HQ;Jb; zuD$ThCIu0qUg^|>;>sJ-X6vMd%TVI~8mpRH;%1CVWUjq^>fJ;{7e5FF+i4AU^aEJO zXrx&cQ;4Nva`i!)8ldMHE5y8R9q(3ruG^$5`(u2H_WmmF%$rLmM2KHXeUpY0!q#%y zU#~dRIys9*mjRN^*I|nSA6Oqu6@dTUUU}HeMUsSdD@{Jr)@l4YCQ4$@v1>W1dk~k=8tO=Tzld{_+_{q4$r-%OFnfHQnvD9{Pb|7e~tg zB!y>NE6TyI644`~PBqo}=GLz^ZZwaSQe2nvLnFI;xoxi^$C^1d=qU`maVB!iHtKyA zg9s;#8QQ}H)^HomO_PxoRy=<6*%D>e=!v2`*FcDvNP-@`<9@F4B=hCLX z?U*=L2k6bVn33gtiw{k8$6>9nj$Z}RH}jC(*>LTCFz8$)XAWM#H?H&xv|(L?Ds-DF zls4J1s$LsZ;4@?3-tMQE(Im}Ml_iyRDkCssa;f(}0?-rxAHUHicDMxLY!>C4(e3!` zE`=JDR->#AeZU;2y%Qwda^onj0m8x^s|qw2Uj=4}Q4<&ooO4(jtBT-gXg>kWK4CUK zUL^HdmXTI(tz#uQw zZ8cC@U{*Nv!c|^>*ubDP&&RFO8fE^UM& zqM3tz*$MaV+h;M>giEJW(8fBG-)wp_oKtmY+Sv3Jz%X zLnHu2y|>B3kz7dHZVn6{T#2{A5iRwDa7>AptUa0GF-4jf8i{64WnC&Y-M~ebX%wHt zl#`g%-I$~8q*3ZucrSL6=1QP6ND=A8-}PPQSCozT*lDa?fjq!rsgEA@Kja`_YgODX z1qaTu{OI-VY__&5J|QTMy^zyU8W>h?q|7|b1LXDHHI(S=sp*?94FeP@{ncL?bXeSB zGv#PP`HCLusqOxhjy_pgFrRdgQc?FR(?}aHWQFG|OG~Gl=&5;f0f!-j795iJ_-na{ zo$C+dKFA}E8H>fh@&uBzlRE)t9Jd+b?ERJ+7~b7xi+O-ZxKXG7>5u+~>smqN4yt5_ zJDGxK%9`nsjYDS;p8bT8 { const theme = useTheme(); - const fillColor = theme.palette.primary.main; + const fillColor = '#FFFFFF'; return ( { p: '12px' }} > -

- - Devias - - - Production - +
+
- diff --git a/frontend/src/theme/colors.js b/frontend/src/theme/colors.js index 0360353..94f89e8 100644 --- a/frontend/src/theme/colors.js +++ b/frontend/src/theme/colors.js @@ -16,12 +16,12 @@ export const neutral = { 100: '#F3F4F6', 200: '#E5E7EB', 300: '#D2D6DB', - 400: '#9DA4AE', + 400: '#FFFFFF', 500: '#6C737F', 600: '#4D5761', - 700: '#2F3746', - 800: '#1C2536', - 900: '#111927' + 700: '#FFFFFF', + 800: '#306d6f', + 900: '#30596f' }; export const indigo = withAlphas({ From 9257e2073b38b90b5e76b9cb20e61d05f82ca9ac Mon Sep 17 00:00:00 2001 From: Leandro Farias Date: Fri, 31 Mar 2023 14:22:22 +0000 Subject: [PATCH 04/14] chore: static frontend layout --- frontend/src/layouts/dashboard/config.js | 63 +++-- .../src/layouts/dashboard/side-nav-item.js | 2 +- frontend/src/layouts/dashboard/top-nav.js | 8 +- frontend/src/pages/auth/login.js | 4 +- frontend/src/pages/devices.js | 234 ++++++++++++++++++ frontend/src/pages/index.js | 93 ++++--- frontend/src/pages/settings.js | 2 +- .../overview/overview-latest-orders.js | 51 ++-- .../overview/overview-tasks-progress.js | 11 +- .../overview/overview-total-customers.js | 7 +- .../src/sections/overview/overview-traffic.js | 33 ++- frontend/src/theme/colors.js | 2 +- 12 files changed, 394 insertions(+), 116 deletions(-) create mode 100644 frontend/src/pages/devices.js diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index 0e7896a..b7ce7f5 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -5,6 +5,7 @@ import ShoppingBagIcon from '@heroicons/react/24/solid/ShoppingBagIcon'; import UserIcon from '@heroicons/react/24/solid/UserIcon'; import UserPlusIcon from '@heroicons/react/24/solid/UserPlusIcon'; import UsersIcon from '@heroicons/react/24/solid/UsersIcon'; +import CpuChip from '@heroicons/react/24/solid/CpuChipIcon'; import XCircleIcon from '@heroicons/react/24/solid/XCircleIcon'; import { SvgIcon } from '@mui/material'; @@ -19,29 +20,11 @@ export const items = [ ) }, { - title: 'Customers', - path: '/customers', + title: 'Devices', + path: '/devices', icon: ( - - - ) - }, - { - title: 'Companies', - path: '/companies', - icon: ( - - - - ) - }, - { - title: 'Account', - path: '/account', - icon: ( - - + ) }, @@ -54,12 +37,24 @@ export const items = [ ) }, +]; + +/* { - title: 'Login', - path: '/auth/login', + title: 'Customers', + path: '/customers', icon: ( - + + + ) + }, + { + title: 'Account', + path: '/account', + icon: ( + + ) }, @@ -72,6 +67,24 @@ export const items = [ ) }, + { + title: 'Login', + path: '/auth/login', + icon: ( + + + + ) + }, + { + title: 'Companies', + path: '/companies', + icon: ( + + + + ) + }, { title: 'Error', path: '/404', @@ -81,4 +94,4 @@ export const items = [ ) } -]; +*/ \ No newline at end of file diff --git a/frontend/src/layouts/dashboard/side-nav-item.js b/frontend/src/layouts/dashboard/side-nav-item.js index 560be19..1b4555e 100644 --- a/frontend/src/layouts/dashboard/side-nav-item.js +++ b/frontend/src/layouts/dashboard/side-nav-item.js @@ -50,7 +50,7 @@ export const SideNavItem = (props) => { justifyContent: 'center', mr: 2, ...(active && { - color: 'primary.main' + color: '#FFFFFF' }) }} > diff --git a/frontend/src/layouts/dashboard/top-nav.js b/frontend/src/layouts/dashboard/top-nav.js index d5a0623..fcd9f69 100644 --- a/frontend/src/layouts/dashboard/top-nav.js +++ b/frontend/src/layouts/dashboard/top-nav.js @@ -78,14 +78,14 @@ export const TopNav = (props) => { direction="row" spacing={2} > - + {/* - - + */} + {/* { - + */} { color="text.secondary" variant="body2" > - Não possui uma conta? + Don't have an account?   { underline="hover" variant="subtitle2" > - Registre-se + Register diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js new file mode 100644 index 0000000..dc241ad --- /dev/null +++ b/frontend/src/pages/devices.js @@ -0,0 +1,234 @@ +import Head from 'next/head'; +import { subDays, subHours } from 'date-fns'; +import { Box, Container, Unstable_Grid2 as Grid } from '@mui/material'; +import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; +import { OverviewBudget } from 'src/sections/overview/overview-budget'; +import { OverviewLatestOrders } from 'src/sections/overview/overview-latest-orders'; +import { OverviewLatestProducts } from 'src/sections/overview/overview-latest-products'; +import { OverviewSales } from 'src/sections/overview/overview-sales'; +import { OverviewTasksProgress } from 'src/sections/overview/overview-tasks-progress'; +import { OverviewTotalCustomers } from 'src/sections/overview/overview-total-customers'; +import { OverviewTotalProfit } from 'src/sections/overview/overview-total-profit'; +import { OverviewTraffic } from 'src/sections/overview/overview-traffic'; + +const now = new Date(); + +const Page = () => ( + <> + + + Oktopus | TR-369 + + + + + + + + + + +); + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; + +/* + + + + + +*/ diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js index 553f184..57710db 100644 --- a/frontend/src/pages/index.js +++ b/frontend/src/pages/index.js @@ -37,12 +37,6 @@ const Page = () => ( sm={6} lg={3} > - ( sm={6} lg={3} > - + + + + + + + + + + + + + +); + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; + +/* ( ]} sx={{ height: '100%' }} /> - - - - - - ( ]} sx={{ height: '100%' }} /> - - - - - -); - -Page.getLayout = (page) => ( - - {page} - -); - -export default Page; +*/ diff --git a/frontend/src/pages/settings.js b/frontend/src/pages/settings.js index 6e8e24d..7dab58a 100644 --- a/frontend/src/pages/settings.js +++ b/frontend/src/pages/settings.js @@ -23,7 +23,7 @@ const Page = () => ( Settings - + {/**/} diff --git a/frontend/src/sections/overview/overview-latest-orders.js b/frontend/src/sections/overview/overview-latest-orders.js index 6d61d0d..2838f85 100644 --- a/frontend/src/sections/overview/overview-latest-orders.js +++ b/frontend/src/sections/overview/overview-latest-orders.js @@ -1,6 +1,7 @@ import { format } from 'date-fns'; import PropTypes from 'prop-types'; import ArrowRightIcon from '@heroicons/react/24/solid/ArrowRightIcon'; +import ArrowTopRightOnSquareIcon from '@heroicons/react/24/solid/ArrowTopRightOnSquareIcon'; import { Box, Button, @@ -19,9 +20,9 @@ import { Scrollbar } from 'src/components/scrollbar'; import { SeverityPill } from 'src/components/severity-pill'; const statusMap = { - pending: 'warning', - delivered: 'success', - refunded: 'error' + Associando: 'warning', + Online: 'success', + Offline: 'error' }; export const OverviewLatestOrders = (props) => { @@ -29,24 +30,27 @@ export const OverviewLatestOrders = (props) => { return ( - + - Order + Model Customer - Date + Vendor Status + + Acessar + @@ -72,6 +76,11 @@ export const OverviewLatestOrders = (props) => { {order.status} + + + + + ); })} @@ -79,21 +88,21 @@ export const OverviewLatestOrders = (props) => {
- - - - + {/* + + + */}
); }; diff --git a/frontend/src/sections/overview/overview-tasks-progress.js b/frontend/src/sections/overview/overview-tasks-progress.js index 955b7e0..6ad4708 100644 --- a/frontend/src/sections/overview/overview-tasks-progress.js +++ b/frontend/src/sections/overview/overview-tasks-progress.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import ListBulletIcon from '@heroicons/react/24/solid/ListBulletIcon'; +import Signal from '@heroicons/react/24/solid/SignalIcon'; import { Avatar, Box, @@ -25,25 +26,25 @@ export const OverviewTasksProgress = (props) => { > - Task Progress + Conexão MQTT - {value}% + {value}ms - + diff --git a/frontend/src/sections/overview/overview-total-customers.js b/frontend/src/sections/overview/overview-total-customers.js index 10949c0..28d99c7 100644 --- a/frontend/src/sections/overview/overview-total-customers.js +++ b/frontend/src/sections/overview/overview-total-customers.js @@ -3,6 +3,7 @@ import ArrowDownIcon from '@heroicons/react/24/solid/ArrowDownIcon'; import ArrowUpIcon from '@heroicons/react/24/solid/ArrowUpIcon'; import UsersIcon from '@heroicons/react/24/solid/UsersIcon'; import { Avatar, Card, CardContent, Stack, SvgIcon, Typography } from '@mui/material'; +import CpuChipIcon from '@heroicons/react/24/solid/CpuChipIcon'; export const OverviewTotalCustomers = (props) => { const { difference, positive = false, sx, value } = props; @@ -21,7 +22,7 @@ export const OverviewTotalCustomers = (props) => { color="text.secondary" variant="overline" > - Total Customers + Total Devices {value} @@ -29,13 +30,13 @@ export const OverviewTotalCustomers = (props) => { - + diff --git a/frontend/src/sections/overview/overview-traffic.js b/frontend/src/sections/overview/overview-traffic.js index 98f0973..7e03824 100644 --- a/frontend/src/sections/overview/overview-traffic.js +++ b/frontend/src/sections/overview/overview-traffic.js @@ -14,18 +14,12 @@ import { } from '@mui/material'; import { Chart } from 'src/components/chart'; -const useChartOptions = (labels) => { +const useChartOptions = (labels,title) => { const theme = useTheme(); - - return { + let options = { chart: { background: 'transparent' }, - colors: [ - theme.palette.primary.main, - theme.palette.success.main, - theme.palette.warning.main - ], dataLabels: { enabled: false }, @@ -60,6 +54,19 @@ const useChartOptions = (labels) => { fillSeriesColor: false } }; + if(title === "Status"){ + options.colors = [ + theme.palette.success.main, + theme.palette.error.main, + ] + }else{ + options.colors = [ + theme.palette.primary.main, + theme.palette.info.main, + theme.palette.warning.main + ] + } + return options }; const iconMap = { @@ -81,12 +88,14 @@ const iconMap = { }; export const OverviewTraffic = (props) => { - const { chartSeries, labels, sx } = props; - const chartOptions = useChartOptions(labels); + const { chartSeries, labels, sx, title } = props; + const chartOptions = useChartOptions(labels,title); return ( - +
+ +
{ {iconMap[label]} {label} diff --git a/frontend/src/theme/colors.js b/frontend/src/theme/colors.js index 94f89e8..e3b59fa 100644 --- a/frontend/src/theme/colors.js +++ b/frontend/src/theme/colors.js @@ -12,7 +12,7 @@ const withAlphas = (color) => { }; export const neutral = { - 50: '#F8F9FA', + 50: '#306d6f', 100: '#F3F4F6', 200: '#E5E7EB', 300: '#D2D6DB', From 93a3199f33389daa226ba5b963dda1967397a9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Mon, 3 Apr 2023 07:11:52 -0300 Subject: [PATCH 05/14] feat(frontend): devices page in progress --- frontend/src/pages/devices.js | 4 ++-- frontend/src/pages/devices/[id].js | 11 +++++++++++ .../src/sections/overview/overview-latest-orders.js | 9 ++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 frontend/src/pages/devices/[id].js diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js index dc241ad..67096c2 100644 --- a/frontend/src/pages/devices.js +++ b/frontend/src/pages/devices.js @@ -43,7 +43,7 @@ const Page = () => ( name: 'Ekaterina Tankova' }, createdAt: 1555016400000, - status: 'Associando' + status: 'Associating' }, { id: '9eaa1c7dd4433f413c308ce2', @@ -73,7 +73,7 @@ const Page = () => ( name: 'Anje Keizer' }, createdAt: 1554757200000, - status: 'Associando' + status: 'Associating' }, { id: '9f974f239d29ede969367103', diff --git a/frontend/src/pages/devices/[id].js b/frontend/src/pages/devices/[id].js new file mode 100644 index 0000000..1e182e0 --- /dev/null +++ b/frontend/src/pages/devices/[id].js @@ -0,0 +1,11 @@ +import { useRouter } from 'next/router' + +const Page = () => { + const router = useRouter() + const { id } = router.query + + return

Device: {id}

+} + + +export default Page; diff --git a/frontend/src/sections/overview/overview-latest-orders.js b/frontend/src/sections/overview/overview-latest-orders.js index 2838f85..3251c95 100644 --- a/frontend/src/sections/overview/overview-latest-orders.js +++ b/frontend/src/sections/overview/overview-latest-orders.js @@ -18,9 +18,10 @@ import { } from '@mui/material'; import { Scrollbar } from 'src/components/scrollbar'; import { SeverityPill } from 'src/components/severity-pill'; +import { useRouter } from 'next/router'; const statusMap = { - Associando: 'warning', + Associating: 'warning', Online: 'success', Offline: 'error' }; @@ -28,6 +29,8 @@ const statusMap = { export const OverviewLatestOrders = (props) => { const { orders = [], sx } = props; + const router = useRouter() + return ( @@ -49,7 +52,7 @@ export const OverviewLatestOrders = (props) => { Status - Acessar + Access @@ -77,7 +80,7 @@ export const OverviewLatestOrders = (props) => { - + router.push("devices/"+order.id)}> From 058b47d461d0bfec2eba4c7786b6ab4a83fec661 Mon Sep 17 00:00:00 2001 From: Leandro Farias Date: Tue, 11 Apr 2023 14:10:56 +0000 Subject: [PATCH 06/14] feat: devices page --- frontend/src/pages/devices.js | 4 +- frontend/src/pages/devices/[id].js | 42 ++++++++- frontend/src/sections/devices/devices-rpc.js | 90 ++++++++++++++++++++ 3 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 frontend/src/sections/devices/devices-rpc.js diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js index 67096c2..ce475d2 100644 --- a/frontend/src/pages/devices.js +++ b/frontend/src/pages/devices.js @@ -24,10 +24,10 @@ const Page = () => ( component="main" sx={{ flexGrow: 1, - py: 8 + py: 8, }} > - + { const router = useRouter() const { id } = router.query - return

Device: {id}

-} - + return( + <> + + + Oktopus | TR-369 + + + + + + + RPC + + {/**/} + < DevicesRPC/> + + + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); export default Page; diff --git a/frontend/src/sections/devices/devices-rpc.js b/frontend/src/sections/devices/devices-rpc.js new file mode 100644 index 0000000..0f4d821 --- /dev/null +++ b/frontend/src/sections/devices/devices-rpc.js @@ -0,0 +1,90 @@ +import { useCallback, useState } from 'react'; +import { + Button, + Card, + CardActions, + CardContent, + CardHeader, + Divider, + Stack, + TextField, + InputLabel, + MenuItem, + Select, + FormControl, + SvgIcon +} from '@mui/material'; +import PaperAirplane from '@heroicons/react/24/solid/PaperAirplaneIcon'; + +export const DevicesRPC = () => { + const [value, setValue] = useState(` + {opa, + teste123:goiaba}` + ) + + const [age, setAge] = useState(1); + + const handleChangeRPC = (event) => { + setAge(event.target.value); + }; + + const handleChange = (event) => { + setValue(event.target.value); + }; + + const handleSubmit = useCallback( + (event) => { + event.preventDefault(); + }, + [] + ); + + return ( +
+ + + + + + + + + + + + + + + + + +
+ ); +}; From 0aa5ca5413707f67f89385e2d003af8c90af2939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Thu, 13 Apr 2023 09:23:24 -0300 Subject: [PATCH 07/14] chore(mochi): change hook from connection to subscription --- backend/services/mochi/cmd/main.go | 33 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/backend/services/mochi/cmd/main.go b/backend/services/mochi/cmd/main.go index da327a7..b91ce7c 100644 --- a/backend/services/mochi/cmd/main.go +++ b/backend/services/mochi/cmd/main.go @@ -12,6 +12,7 @@ import ( "log" "os" "os/signal" + "strings" "syscall" "github.com/mochi-co/mqtt/v2" @@ -121,7 +122,7 @@ func (h *MyHook) ID() string { func (h *MyHook) Provides(b byte) bool { return bytes.Contains([]byte{ - mqtt.OnConnect, + mqtt.OnSubscribed, }, []byte{b}) } @@ -130,18 +131,22 @@ func (h *MyHook) Init(config any) error { return nil } -func (h *MyHook) OnConnect(cl *mqtt.Client, pk packets.Packet) { - log.Println("new connection") - var clUser string - if len(cl.Properties.Props.User) > 0 { - clUser = cl.Properties.Props.User[0].Val - } - if clUser != "" { - log.Println("new device:", clUser) - err := server.Publish("oktopus/devices", []byte(clUser), false, 1) - if err != nil { - log.Println("server publish error: ", err) - } - } +func (h *MyHook) OnSubscribed(cl *mqtt.Client, pk packets.Packet, reasonCodes []byte) { + // Verifies if it's a device who is subscribed + if strings.Contains(pk.Filters[0].Filter, "oktopus/v1/agent") { + var clUser string + if len(cl.Properties.Props.User) > 0 { + clUser = cl.Properties.Props.User[0].Val + } + + if clUser != "" { + log.Println("new device:", clUser) + err := server.Publish("oktopus/devices", []byte(clUser), false, 1) + if err != nil { + log.Println("server publish error: ", err) + } + } + + } } From ec64cd9679c6142b2ffc98f97d4336f493cce5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Thu, 13 Apr 2023 09:54:04 -0300 Subject: [PATCH 08/14] chore: set boxes with shadow --- frontend/src/theme/create-components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/theme/create-components.js b/frontend/src/theme/create-components.js index 5357302..52282e0 100644 --- a/frontend/src/theme/create-components.js +++ b/frontend/src/theme/create-components.js @@ -54,7 +54,7 @@ export function createComponents(config) { root: { borderRadius: 20, [`&.${paperClasses.elevation1}`]: { - boxShadow: '0px 5px 22px rgba(0, 0, 0, 0.04), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.03)' + boxShadow: '0px 5px 22px rgba(0, 0, 0, 0.1), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.03)' } } } From 45364aec39fa1863e1bbb6d4a379148b175a3b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Thu, 13 Apr 2023 09:54:48 -0300 Subject: [PATCH 09/14] feat:save devices at database --- .../services/controller/cmd/oktopus/main.go | 8 +++-- backend/services/controller/go.mod | 19 ++++++++-- backend/services/controller/internal/db/db.go | 28 +++++++++++++++ .../services/controller/internal/db/device.go | 21 +++++++++++ .../controller/internal/mqtt/mqtt-server.go | 30 ---------------- .../internal/mqtt/{mqtt-client.go => mqtt.go} | 35 ++++++++++++------- 6 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 backend/services/controller/internal/db/db.go create mode 100644 backend/services/controller/internal/db/device.go delete mode 100755 backend/services/controller/internal/mqtt/mqtt-server.go rename backend/services/controller/internal/mqtt/{mqtt-client.go => mqtt.go} (89%) diff --git a/backend/services/controller/cmd/oktopus/main.go b/backend/services/controller/cmd/oktopus/main.go index f56aa37..0423df9 100755 --- a/backend/services/controller/cmd/oktopus/main.go +++ b/backend/services/controller/cmd/oktopus/main.go @@ -5,6 +5,7 @@ package main import ( "context" "flag" + "github.com/leandrofars/oktopus/internal/db" "log" "os" "os/signal" @@ -35,6 +36,7 @@ func main() { flBrokerPassword := flag.String("P", "", "Mqtt broker password") flBrokerClientId := flag.String("i", "", "A clientid for the Mqtt connection") flBrokerQos := flag.Int("q", 2, "Quality of service of mqtt messages delivery") + flAddrDB := flag.String("mongo", "mongodb://localhost:27017/", "MongoDB URI") flHelp := flag.Bool("help", false, "Help") flag.Parse() @@ -47,8 +49,8 @@ func main() { This context suppress our needs, but we can use a more sofisticate approach with cancel and timeout options passing it through paho mqtt functions. */ - ctx := context.Background() - + ctx, cancel := context.WithCancel(context.Background()) + database := db.NewDatabase(ctx, *flAddrDB) /* If you want to use another message protocol just make it implement Broker interface. */ @@ -63,11 +65,13 @@ func main() { SubTopic: *flSubTopic, DevicesTopic: *flDevicesTopic, CA: *flTlsCert, + DB: database, } mtp.MtpService(&mqttClient, done) <-done + cancel() log.Println("(⌐■_■) Oktopus is out!") diff --git a/backend/services/controller/go.mod b/backend/services/controller/go.mod index e2dc489..e8d211b 100755 --- a/backend/services/controller/go.mod +++ b/backend/services/controller/go.mod @@ -2,9 +2,22 @@ module github.com/leandrofars/oktopus go 1.18 -require google.golang.org/protobuf v1.28.1 +require ( + github.com/eclipse/paho.golang v0.10.0 + go.mongodb.org/mongo-driver v1.11.3 + google.golang.org/protobuf v1.28.1 +) require ( - github.com/eclipse/paho.golang v0.10.0 // indirect - golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.1 // indirect + github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/text v0.3.7 // indirect ) diff --git a/backend/services/controller/internal/db/db.go b/backend/services/controller/internal/db/db.go new file mode 100644 index 0000000..5f217f7 --- /dev/null +++ b/backend/services/controller/internal/db/db.go @@ -0,0 +1,28 @@ +package db + +import ( + "context" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "log" +) + +type Database struct { + devices *mongo.Collection + ctx context.Context +} + +func NewDatabase(ctx context.Context, mongoUri string) Database { + var db Database + clientOptions := options.Client().ApplyURI(mongoUri) + client, err := mongo.Connect(ctx, clientOptions) + if err != nil { + log.Fatal(err) + } + + log.Println("Connected to MongoDB-->", mongoUri) + devices := client.Database("oktopus").Collection("devices") + db.devices = devices + db.ctx = ctx + return db +} diff --git a/backend/services/controller/internal/db/device.go b/backend/services/controller/internal/db/device.go new file mode 100644 index 0000000..6883b2c --- /dev/null +++ b/backend/services/controller/internal/db/device.go @@ -0,0 +1,21 @@ +package db + +type Device struct { + Model string + Customer string + Vendor string + Version string +} + +func (d *Database) CreateDevice(device Device) error { + _, err := d.devices.InsertOne(d.ctx, device, nil) + return err +} + +func (d *Database) RetrieveDevice() { + +} + +func (d *Database) DeleteDevice() { + +} diff --git a/backend/services/controller/internal/mqtt/mqtt-server.go b/backend/services/controller/internal/mqtt/mqtt-server.go deleted file mode 100755 index 40583aa..0000000 --- a/backend/services/controller/internal/mqtt/mqtt-server.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - Runs MQTT broker trough a Docker container. - Better approach would be to use docker api to Go language, but os/exec lib is already enough for our purpose, - since it's more convenient and easier to use docker shell commands, and it's already a start point. -*/ -package mqtt - -import ( - "log" - "os/exec" -) - -// Get Mqtt Broker up and running -func StartMqttBroker() { - - //TODO: Start Container through Docker SDK for GO, eliminating docker-compose and shell comands. - //TODO: Create Broker with user, password and CA certificate. - //TODO: Set MQTTv5 CONNACK packet with topic for agent to use. - - cmd := exec.Command("sudo", "docker", "compose", "-f", "internal/mosquitto/docker-compose.yml", "up", "-d") - - err := cmd.Run() - - if err != nil { - log.Fatal(err.Error()) - return - } - - log.Println("Broker Mqtt Up and Running!") -} diff --git a/backend/services/controller/internal/mqtt/mqtt-client.go b/backend/services/controller/internal/mqtt/mqtt.go similarity index 89% rename from backend/services/controller/internal/mqtt/mqtt-client.go rename to backend/services/controller/internal/mqtt/mqtt.go index 9df597f..a30b206 100644 --- a/backend/services/controller/internal/mqtt/mqtt-client.go +++ b/backend/services/controller/internal/mqtt/mqtt.go @@ -4,18 +4,17 @@ import ( "context" "crypto/tls" "crypto/x509" + "github.com/eclipse/paho.golang/paho" + "github.com/leandrofars/oktopus/internal/db" usp_msg "github.com/leandrofars/oktopus/internal/usp_message" "github.com/leandrofars/oktopus/internal/usp_record" + "github.com/leandrofars/oktopus/internal/utils" "google.golang.org/protobuf/proto" "io/ioutil" "log" "net" "strings" "sync" - "time" - - "github.com/eclipse/paho.golang/paho" - "github.com/leandrofars/oktopus/internal/utils" ) type Mqtt struct { @@ -29,6 +28,7 @@ type Mqtt struct { SubTopic string DevicesTopic string CA string + DB db.Database } var c *paho.Client @@ -53,10 +53,6 @@ func (m *Mqtt) Connect() { // Sets global client to be used by other mqtt functions c = clientConfig - if conn.ReasonCode != 0 { - log.Fatalf("Failed to connect to %s : %d - %s", m.Addr, conn.ReasonCode, conn.Properties.ReasonString) - } - log.Printf("Connected to broker--> %s:%s", m.Addr, m.Port) } @@ -238,8 +234,12 @@ func (m *Mqtt) handleNewDevice(deviceMac string) { Request: &usp_msg.Request{ ReqType: &usp_msg.Request_Get{ Get: &usp_msg.Get{ - ParamPaths: []string{"Device.DeviceInfo."}, - MaxDepth: 1, + ParamPaths: []string{ + "Device.DeviceInfo.Manufacturer", + "Device.DeviceInfo.ModelName", + "Device.DeviceInfo.SoftwareVersion", + }, + MaxDepth: 1, }, }, }, @@ -263,7 +263,6 @@ func (m *Mqtt) handleNewDevice(deviceMac string) { if err != nil { log.Fatalln("Failed to encode address book:", err) } - time.Sleep(5 * time.Second) m.Publish(tr369Message, "oktopus/v1/agent/"+deviceMac, "oktopus/v1/controller/"+deviceMac) } @@ -280,5 +279,17 @@ func (m *Mqtt) handleDevicesResponse(p []byte) { log.Fatal(err) } - log.Printf("Received a usp_message: %s\n", message.String()) + var device db.Device + msg := message.Body.MsgBody.(*usp_msg.Body_Response).Response.GetGetResp() + + device.Vendor = msg.ReqPathResults[0].ResolvedPathResults[0].ResultParams["Manufacturer"] + device.Model = msg.ReqPathResults[1].ResolvedPathResults[0].ResultParams["ModelName"] + device.Version = msg.ReqPathResults[2].ResolvedPathResults[0].ResultParams["SoftwareVersion"] + + err = m.DB.CreateDevice(device) + if err != nil { + log.Fatal(err) + } + + log.Printf("New device saved at database") } From fd25ed1bf97d78869a11ecde94715ad241d16137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Thu, 13 Apr 2023 09:54:48 -0300 Subject: [PATCH 10/14] feat:save devices at database --- .../services/controller/cmd/oktopus/main.go | 8 +++- backend/services/controller/go.mod | 19 ++++++-- backend/services/controller/go.sum | 48 ++++++++++++++++++- backend/services/controller/internal/db/db.go | 28 +++++++++++ .../services/controller/internal/db/device.go | 21 ++++++++ .../controller/internal/mqtt/mqtt-server.go | 30 ------------ .../internal/mqtt/{mqtt-client.go => mqtt.go} | 35 +++++++++----- 7 files changed, 141 insertions(+), 48 deletions(-) create mode 100644 backend/services/controller/internal/db/db.go create mode 100644 backend/services/controller/internal/db/device.go delete mode 100755 backend/services/controller/internal/mqtt/mqtt-server.go rename backend/services/controller/internal/mqtt/{mqtt-client.go => mqtt.go} (89%) diff --git a/backend/services/controller/cmd/oktopus/main.go b/backend/services/controller/cmd/oktopus/main.go index f56aa37..0423df9 100755 --- a/backend/services/controller/cmd/oktopus/main.go +++ b/backend/services/controller/cmd/oktopus/main.go @@ -5,6 +5,7 @@ package main import ( "context" "flag" + "github.com/leandrofars/oktopus/internal/db" "log" "os" "os/signal" @@ -35,6 +36,7 @@ func main() { flBrokerPassword := flag.String("P", "", "Mqtt broker password") flBrokerClientId := flag.String("i", "", "A clientid for the Mqtt connection") flBrokerQos := flag.Int("q", 2, "Quality of service of mqtt messages delivery") + flAddrDB := flag.String("mongo", "mongodb://localhost:27017/", "MongoDB URI") flHelp := flag.Bool("help", false, "Help") flag.Parse() @@ -47,8 +49,8 @@ func main() { This context suppress our needs, but we can use a more sofisticate approach with cancel and timeout options passing it through paho mqtt functions. */ - ctx := context.Background() - + ctx, cancel := context.WithCancel(context.Background()) + database := db.NewDatabase(ctx, *flAddrDB) /* If you want to use another message protocol just make it implement Broker interface. */ @@ -63,11 +65,13 @@ func main() { SubTopic: *flSubTopic, DevicesTopic: *flDevicesTopic, CA: *flTlsCert, + DB: database, } mtp.MtpService(&mqttClient, done) <-done + cancel() log.Println("(⌐■_■) Oktopus is out!") diff --git a/backend/services/controller/go.mod b/backend/services/controller/go.mod index e2dc489..e8d211b 100755 --- a/backend/services/controller/go.mod +++ b/backend/services/controller/go.mod @@ -2,9 +2,22 @@ module github.com/leandrofars/oktopus go 1.18 -require google.golang.org/protobuf v1.28.1 +require ( + github.com/eclipse/paho.golang v0.10.0 + go.mongodb.org/mongo-driver v1.11.3 + google.golang.org/protobuf v1.28.1 +) require ( - github.com/eclipse/paho.golang v0.10.0 // indirect - golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.1 // indirect + github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/text v0.3.7 // indirect ) diff --git a/backend/services/controller/go.sum b/backend/services/controller/go.sum index 1086b2f..fc404e7 100644 --- a/backend/services/controller/go.sum +++ b/backend/services/controller/go.sum @@ -1,19 +1,65 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/eclipse/paho.golang v0.10.0 h1:oUGPjRwWcZQRgDD9wVDV7y7i7yBSxts3vcvcNJo8B4Q= github.com/eclipse/paho.golang v0.10.0/go.mod h1:rhrV37IEwauUyx8FHrvmXOKo+QRKng5ncoN1vJiJMcs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= +go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/services/controller/internal/db/db.go b/backend/services/controller/internal/db/db.go new file mode 100644 index 0000000..5f217f7 --- /dev/null +++ b/backend/services/controller/internal/db/db.go @@ -0,0 +1,28 @@ +package db + +import ( + "context" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "log" +) + +type Database struct { + devices *mongo.Collection + ctx context.Context +} + +func NewDatabase(ctx context.Context, mongoUri string) Database { + var db Database + clientOptions := options.Client().ApplyURI(mongoUri) + client, err := mongo.Connect(ctx, clientOptions) + if err != nil { + log.Fatal(err) + } + + log.Println("Connected to MongoDB-->", mongoUri) + devices := client.Database("oktopus").Collection("devices") + db.devices = devices + db.ctx = ctx + return db +} diff --git a/backend/services/controller/internal/db/device.go b/backend/services/controller/internal/db/device.go new file mode 100644 index 0000000..6883b2c --- /dev/null +++ b/backend/services/controller/internal/db/device.go @@ -0,0 +1,21 @@ +package db + +type Device struct { + Model string + Customer string + Vendor string + Version string +} + +func (d *Database) CreateDevice(device Device) error { + _, err := d.devices.InsertOne(d.ctx, device, nil) + return err +} + +func (d *Database) RetrieveDevice() { + +} + +func (d *Database) DeleteDevice() { + +} diff --git a/backend/services/controller/internal/mqtt/mqtt-server.go b/backend/services/controller/internal/mqtt/mqtt-server.go deleted file mode 100755 index 40583aa..0000000 --- a/backend/services/controller/internal/mqtt/mqtt-server.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - Runs MQTT broker trough a Docker container. - Better approach would be to use docker api to Go language, but os/exec lib is already enough for our purpose, - since it's more convenient and easier to use docker shell commands, and it's already a start point. -*/ -package mqtt - -import ( - "log" - "os/exec" -) - -// Get Mqtt Broker up and running -func StartMqttBroker() { - - //TODO: Start Container through Docker SDK for GO, eliminating docker-compose and shell comands. - //TODO: Create Broker with user, password and CA certificate. - //TODO: Set MQTTv5 CONNACK packet with topic for agent to use. - - cmd := exec.Command("sudo", "docker", "compose", "-f", "internal/mosquitto/docker-compose.yml", "up", "-d") - - err := cmd.Run() - - if err != nil { - log.Fatal(err.Error()) - return - } - - log.Println("Broker Mqtt Up and Running!") -} diff --git a/backend/services/controller/internal/mqtt/mqtt-client.go b/backend/services/controller/internal/mqtt/mqtt.go similarity index 89% rename from backend/services/controller/internal/mqtt/mqtt-client.go rename to backend/services/controller/internal/mqtt/mqtt.go index 9df597f..a30b206 100644 --- a/backend/services/controller/internal/mqtt/mqtt-client.go +++ b/backend/services/controller/internal/mqtt/mqtt.go @@ -4,18 +4,17 @@ import ( "context" "crypto/tls" "crypto/x509" + "github.com/eclipse/paho.golang/paho" + "github.com/leandrofars/oktopus/internal/db" usp_msg "github.com/leandrofars/oktopus/internal/usp_message" "github.com/leandrofars/oktopus/internal/usp_record" + "github.com/leandrofars/oktopus/internal/utils" "google.golang.org/protobuf/proto" "io/ioutil" "log" "net" "strings" "sync" - "time" - - "github.com/eclipse/paho.golang/paho" - "github.com/leandrofars/oktopus/internal/utils" ) type Mqtt struct { @@ -29,6 +28,7 @@ type Mqtt struct { SubTopic string DevicesTopic string CA string + DB db.Database } var c *paho.Client @@ -53,10 +53,6 @@ func (m *Mqtt) Connect() { // Sets global client to be used by other mqtt functions c = clientConfig - if conn.ReasonCode != 0 { - log.Fatalf("Failed to connect to %s : %d - %s", m.Addr, conn.ReasonCode, conn.Properties.ReasonString) - } - log.Printf("Connected to broker--> %s:%s", m.Addr, m.Port) } @@ -238,8 +234,12 @@ func (m *Mqtt) handleNewDevice(deviceMac string) { Request: &usp_msg.Request{ ReqType: &usp_msg.Request_Get{ Get: &usp_msg.Get{ - ParamPaths: []string{"Device.DeviceInfo."}, - MaxDepth: 1, + ParamPaths: []string{ + "Device.DeviceInfo.Manufacturer", + "Device.DeviceInfo.ModelName", + "Device.DeviceInfo.SoftwareVersion", + }, + MaxDepth: 1, }, }, }, @@ -263,7 +263,6 @@ func (m *Mqtt) handleNewDevice(deviceMac string) { if err != nil { log.Fatalln("Failed to encode address book:", err) } - time.Sleep(5 * time.Second) m.Publish(tr369Message, "oktopus/v1/agent/"+deviceMac, "oktopus/v1/controller/"+deviceMac) } @@ -280,5 +279,17 @@ func (m *Mqtt) handleDevicesResponse(p []byte) { log.Fatal(err) } - log.Printf("Received a usp_message: %s\n", message.String()) + var device db.Device + msg := message.Body.MsgBody.(*usp_msg.Body_Response).Response.GetGetResp() + + device.Vendor = msg.ReqPathResults[0].ResolvedPathResults[0].ResultParams["Manufacturer"] + device.Model = msg.ReqPathResults[1].ResolvedPathResults[0].ResultParams["ModelName"] + device.Version = msg.ReqPathResults[2].ResolvedPathResults[0].ResultParams["SoftwareVersion"] + + err = m.DB.CreateDevice(device) + if err != nil { + log.Fatal(err) + } + + log.Printf("New device saved at database") } From 83c88e87bed30c6c1436823d7e814ae8ef766f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Thu, 20 Apr 2023 11:34:48 -0300 Subject: [PATCH 11/14] feat: api structure --- .../services/controller/internal/api/api.go | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 backend/services/controller/internal/api/api.go diff --git a/backend/services/controller/internal/api/api.go b/backend/services/controller/internal/api/api.go new file mode 100644 index 0000000..28969b4 --- /dev/null +++ b/backend/services/controller/internal/api/api.go @@ -0,0 +1,63 @@ +package api + +import ( + "encoding/json" + "github.com/gorilla/mux" + "github.com/leandrofars/oktopus/internal/db" + "log" + "net/http" + "time" +) + +type Api struct { + Port string + Db db.Database +} + +func NewApi(port string, db db.Database) Api { + return Api{ + Port: port, + Db: db, + } +} + +func StartApi(a Api) { + r := mux.NewRouter() + + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + return + }) + r.HandleFunc("/devices", a.retrieveDevices) + //r.HandleFunc("/devices/{sn}", a.devicesMessaging) + + srv := &http.Server{ + Addr: "0.0.0.0:" + a.Port, + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 15, + ReadTimeout: time.Second * 15, + IdleTimeout: time.Second * 60, + Handler: r, // Pass our instance of gorilla/mux in. + } + + // Run our server in a goroutine so that it doesn't block. + go func() { + if err := srv.ListenAndServe(); err != nil { + log.Println(err) + } + }() +} + +func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) { + devices, err := a.Db.RetrieveDevices() + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + err = json.NewEncoder(w).Encode(devices) + if err != nil { + log.Println(err) + } + + return +} From 0ff00b9ca1545a6cbbd3b87817de1b41fe52310c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Thu, 20 Apr 2023 11:34:48 -0300 Subject: [PATCH 12/14] feat: api structure --- backend/services/controller/cmd/oktopus/main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/services/controller/cmd/oktopus/main.go b/backend/services/controller/cmd/oktopus/main.go index 0423df9..4065f2d 100755 --- a/backend/services/controller/cmd/oktopus/main.go +++ b/backend/services/controller/cmd/oktopus/main.go @@ -5,6 +5,7 @@ package main import ( "context" "flag" + "github.com/leandrofars/oktopus/internal/api" "github.com/leandrofars/oktopus/internal/db" "log" "os" @@ -37,6 +38,7 @@ func main() { flBrokerClientId := flag.String("i", "", "A clientid for the Mqtt connection") flBrokerQos := flag.Int("q", 2, "Quality of service of mqtt messages delivery") flAddrDB := flag.String("mongo", "mongodb://localhost:27017/", "MongoDB URI") + flApiPort := flag.String("ap", "8000", "Rest api port") flHelp := flag.Bool("help", false, "Help") flag.Parse() @@ -69,6 +71,8 @@ func main() { } mtp.MtpService(&mqttClient, done) + a := api.NewApi(*flApiPort, database) + api.StartApi(a) <-done cancel() From 7c40982a00e9b874fd852ef9f9aa8aff98fd424b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= Date: Thu, 20 Apr 2023 11:37:20 -0300 Subject: [PATCH 13/14] feat: save devices at database --- backend/services/controller/go.mod | 1 + backend/services/controller/go.sum | 2 ++ .../services/controller/internal/db/device.go | 35 +++++++++++++++++-- .../services/controller/internal/mqtt/mqtt.go | 4 +-- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/backend/services/controller/go.mod b/backend/services/controller/go.mod index e8d211b..8457d1a 100755 --- a/backend/services/controller/go.mod +++ b/backend/services/controller/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/golang/snappy v0.0.1 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/backend/services/controller/go.sum b/backend/services/controller/go.sum index fc404e7..19993ba 100644 --- a/backend/services/controller/go.sum +++ b/backend/services/controller/go.sum @@ -9,6 +9,8 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= diff --git a/backend/services/controller/internal/db/device.go b/backend/services/controller/internal/db/device.go index 6883b2c..e152afc 100644 --- a/backend/services/controller/internal/db/device.go +++ b/backend/services/controller/internal/db/device.go @@ -1,6 +1,14 @@ package db +import ( + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "log" +) + type Device struct { + SN string Model string Customer string Vendor string @@ -8,12 +16,33 @@ type Device struct { } func (d *Database) CreateDevice(device Device) error { - _, err := d.devices.InsertOne(d.ctx, device, nil) + var result bson.M + opts := options.FindOneAndReplace().SetUpsert(true) + err := d.devices.FindOneAndReplace(d.ctx, bson.D{{"sn", device.SN}}, device, opts).Decode(&result) + if err != nil { + if err == mongo.ErrNoDocuments { + log.Printf("New device %s added to database", device.SN) + return nil + } + log.Fatal(err) + } + log.Printf("Device %s already existed, and got replaced for new info", device.SN) return err } -func (d *Database) RetrieveDevice() { - +func (d *Database) RetrieveDevices() ([]Device, error) { + var results []Device + //TODO: filter devices by user ownership + cursor, err := d.devices.Find(d.ctx, bson.D{}, nil) + if err != nil { + log.Println(err) + return nil, err + } + if err = cursor.All(d.ctx, &results); err != nil { + log.Println(err) + return nil, err + } + return results, nil } func (d *Database) DeleteDevice() { diff --git a/backend/services/controller/internal/mqtt/mqtt.go b/backend/services/controller/internal/mqtt/mqtt.go index a30b206..662d886 100644 --- a/backend/services/controller/internal/mqtt/mqtt.go +++ b/backend/services/controller/internal/mqtt/mqtt.go @@ -238,6 +238,7 @@ func (m *Mqtt) handleNewDevice(deviceMac string) { "Device.DeviceInfo.Manufacturer", "Device.DeviceInfo.ModelName", "Device.DeviceInfo.SoftwareVersion", + "Device.DeviceInfo.SerialNumber", }, MaxDepth: 1, }, @@ -285,11 +286,10 @@ func (m *Mqtt) handleDevicesResponse(p []byte) { device.Vendor = msg.ReqPathResults[0].ResolvedPathResults[0].ResultParams["Manufacturer"] device.Model = msg.ReqPathResults[1].ResolvedPathResults[0].ResultParams["ModelName"] device.Version = msg.ReqPathResults[2].ResolvedPathResults[0].ResultParams["SoftwareVersion"] + device.SN = msg.ReqPathResults[3].ResolvedPathResults[0].ResultParams["SerialNumber"] err = m.DB.CreateDevice(device) if err != nil { log.Fatal(err) } - - log.Printf("New device saved at database") } From 2d7da446fcf38d9e79ba5b6a47ab6deeefaa4dd9 Mon Sep 17 00:00:00 2001 From: Leandro Farias Date: Thu, 20 Apr 2023 14:46:53 +0000 Subject: [PATCH 14/14] feat: circular progress loading component --- frontend/src/pages/devices/[id].js | 7 ++++--- frontend/src/sections/devices/devices-rpc.js | 22 +++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/devices/[id].js b/frontend/src/pages/devices/[id].js index 634263f..c3d2a74 100644 --- a/frontend/src/pages/devices/[id].js +++ b/frontend/src/pages/devices/[id].js @@ -7,6 +7,7 @@ import { DevicesRPC } from 'src/sections/devices/devices-rpc'; const Page = () => { const router = useRouter() const { id } = router.query + return( <> @@ -19,7 +20,7 @@ const Page = () => { component="main" sx={{ flexGrow: 1, - py: 8, + py: 0, }} > @@ -28,7 +29,7 @@ const Page = () => { RPC
{/**/} - < DevicesRPC/> + < DevicesRPC /> @@ -42,4 +43,4 @@ Page.getLayout = (page) => ( ); -export default Page; +export default Page; \ No newline at end of file diff --git a/frontend/src/sections/devices/devices-rpc.js b/frontend/src/sections/devices/devices-rpc.js index 0f4d821..556d7b2 100644 --- a/frontend/src/sections/devices/devices-rpc.js +++ b/frontend/src/sections/devices/devices-rpc.js @@ -15,8 +15,21 @@ import { SvgIcon } from '@mui/material'; import PaperAirplane from '@heroicons/react/24/solid/PaperAirplaneIcon'; +import CircularProgress from '@mui/material/CircularProgress'; +import Backdrop from '@mui/material/Backdrop'; + export const DevicesRPC = () => { + +const [open, setOpen] = useState(false); + +const handleClose = () => { + setOpen(false); +}; +const handleOpen = () => { + setOpen(true); +}; + const [value, setValue] = useState(` {opa, teste123:goiaba}` @@ -80,10 +93,17 @@ export const DevicesRPC = () => { - + theme.zIndex.drawer + 1 }} + open={open} + onClick={handleClose} + > + + );