From b6ebf8eeabad182d75dd5aea0ae1ce4856b81a15 Mon Sep 17 00:00:00 2001 From: kwatters Date: Tue, 13 Jun 2023 17:07:33 -0400 Subject: [PATCH 01/54] initial cut at indexing mp3 files in solr using the file connector and document pipeline with tika --- pom.xml | 18 ++++--- .../document/connector/AbstractConnector.java | 7 --- .../document/transformer/TextExtractor.java | 7 ++- .../document/workflow/Workflow.java | 9 ++-- .../document/workflow/WorkflowServer.java | 12 +++-- .../document/workflow/WorkflowWorker.java | 6 ++- .../myrobotlab/service/DocumentPipeline.java | 53 ++++++++++++------- .../org/myrobotlab/service/FileConnector.java | 8 +-- .../java/org/myrobotlab/service/Solr.java | 1 + .../service/interfaces/DocumentListener.java | 1 + .../service/interfaces/DocumentPublisher.java | 14 ++++- .../service/meta/DocumentPipelineMeta.java | 4 +- .../resource/WebGui/app/service/js/SolrGui.js | 2 +- .../WebGui/app/service/views/SolrGui.html | 2 +- .../document/connector/WikipediaIndexer.java | 2 +- .../org/myrobotlab/service/HarryTest.java | 2 +- .../interfaces/AbstractConnectorTest.java | 2 +- 17 files changed, 93 insertions(+), 57 deletions(-) diff --git a/pom.xml b/pom.xml index e4f8268e24..e321c3c493 100644 --- a/pom.xml +++ b/pom.xml @@ -305,12 +305,18 @@ - - org.apache.tika - tika-core - 1.22 - provided - + + + org.apache.tika + tika-core + 2.8.0 + + + org.apache.tika + tika-parser-audiovideo-module + 2.8.0 + + org.apache.opennlp opennlp-tools diff --git a/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java b/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java index 46c96637b7..b008cf6be4 100644 --- a/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java +++ b/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java @@ -126,13 +126,6 @@ public List publishDocuments(List batch) { return batch; } - @Override - public void addDocumentListener(DocumentListener listener) { - addListener("publishDocument", listener.getName(), "onDocument"); - addListener("publishDocuments", listener.getName(), "onDocuments"); - addListener("publishFlush", listener.getName(), "onFlush"); - } - @Override public ConnectorState getConnectorState() { return state; diff --git a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java index b030f2b9c8..06810fd420 100644 --- a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java +++ b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java @@ -16,7 +16,6 @@ import org.myrobotlab.document.Document; import org.myrobotlab.logging.LoggerFactory; import org.slf4j.Logger; -import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; /** @@ -85,16 +84,16 @@ public List processDocument(Document doc) { Metadata metadata = new Metadata(); StringWriter textData = new StringWriter(); - ContentHandler bch = new BodyContentHandler(textData); + BodyContentHandler bch = new BodyContentHandler(textData); try { parser.parse(binaryData, bch, metadata, parseCtx); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); - } catch (SAXException e) { + } catch (TikaException e) { // TODO Auto-generated catch block e.printStackTrace(); - } catch (TikaException e) { + } catch (SAXException e) { // TODO Auto-generated catch block e.printStackTrace(); } diff --git a/src/main/java/org/myrobotlab/document/workflow/Workflow.java b/src/main/java/org/myrobotlab/document/workflow/Workflow.java index f7a93411c7..c6ac02ff4d 100644 --- a/src/main/java/org/myrobotlab/document/workflow/Workflow.java +++ b/src/main/java/org/myrobotlab/document/workflow/Workflow.java @@ -5,6 +5,7 @@ import org.myrobotlab.document.Document; import org.myrobotlab.document.transformer.WorkflowConfiguration; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.DocumentPipeline; import org.slf4j.Logger; /** @@ -41,18 +42,18 @@ public Workflow(WorkflowConfiguration workflowConfig) throws ClassNotFoundExcept } // initialize the workflow - public void initialize() { + public void initialize(DocumentPipeline pipeline) { workers = new WorkflowWorker[numWorkerThreads]; for (int i = 0; i < numWorkerThreads; i++) { - initializeWorkerThread(i); + initializeWorkerThread(i, pipeline); } } // init the worker threads - private void initializeWorkerThread(int threadNum) { + private void initializeWorkerThread(int threadNum, DocumentPipeline pipeline) { WorkflowWorker worker = null; try { - worker = new WorkflowWorker(workflowConfig, queue, Integer.toString(threadNum)); + worker = new WorkflowWorker(workflowConfig, queue, Integer.toString(threadNum), pipeline); } catch (ClassNotFoundException e) { // TODO: better handling? log.warn("Error starting the worker thread. {}", e.getLocalizedMessage()); diff --git a/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java b/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java index d71e2d0005..8c12595035 100644 --- a/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java +++ b/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java @@ -3,22 +3,25 @@ import java.util.HashMap; import org.myrobotlab.document.transformer.WorkflowConfiguration; +import org.myrobotlab.service.DocumentPipeline; public class WorkflowServer { private static WorkflowServer instance = null; private HashMap workflowMap; + private final DocumentPipeline pipeline; // singleton, the constructor is private. - private WorkflowServer() { + private WorkflowServer(DocumentPipeline pipeline) { workflowMap = new HashMap(); + this.pipeline = pipeline; } // This is a singleton also - public static WorkflowServer getInstance() { + public static WorkflowServer getInstance(DocumentPipeline pipeline) { if (instance == null) { - instance = new WorkflowServer(); + instance = new WorkflowServer(pipeline); return instance; } else { return instance; @@ -31,7 +34,7 @@ public static WorkflowServer getInstance() { public void addWorkflow(WorkflowConfiguration config) throws ClassNotFoundException { Workflow w = new Workflow(config); - w.initialize(); + w.initialize(pipeline); workflowMap.put(w.getName(), w); } @@ -59,5 +62,4 @@ public String[] listWorkflows() { workflowMap.keySet().toArray(ws); return ws; } - } diff --git a/src/main/java/org/myrobotlab/document/workflow/WorkflowWorker.java b/src/main/java/org/myrobotlab/document/workflow/WorkflowWorker.java index 6ccc170402..fc7a3e48cd 100644 --- a/src/main/java/org/myrobotlab/document/workflow/WorkflowWorker.java +++ b/src/main/java/org/myrobotlab/document/workflow/WorkflowWorker.java @@ -10,6 +10,7 @@ import org.myrobotlab.document.transformer.StageConfiguration; import org.myrobotlab.document.transformer.WorkflowConfiguration; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.DocumentPipeline; import org.slf4j.Logger; /** @@ -24,10 +25,12 @@ public class WorkflowWorker extends Thread { private final LinkedBlockingQueue queue; - WorkflowWorker(WorkflowConfiguration workflowConfig, LinkedBlockingQueue queue, String workerId) throws ClassNotFoundException { + private final DocumentPipeline pipeline; + WorkflowWorker(WorkflowConfiguration workflowConfig, LinkedBlockingQueue queue, String workerId, DocumentPipeline pipeline) throws ClassNotFoundException { // set the thread name this.setName("WorkflowWorker-" + workflowConfig.getName() + "-" + workerId); this.queue = queue; + this.pipeline = pipeline; stages = new ArrayList(); for (StageConfiguration stageConf : workflowConfig.getStages()) { String stageClass = stageConf.getStageClass().trim(); @@ -63,6 +66,7 @@ public void run() { processing = true; // process from the start of the workflow processDocumentInternal(doc, 0); + pipeline.invoke("publishDocument", doc); processing = false; } } catch (Exception e) { diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index dd25011b85..73cd85eca3 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -10,6 +10,8 @@ import org.myrobotlab.document.workflow.WorkflowMessage; import org.myrobotlab.document.workflow.WorkflowServer; import org.myrobotlab.framework.Service; +import org.myrobotlab.logging.Level; +import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.interfaces.DocumentListener; import org.myrobotlab.service.interfaces.DocumentPublisher; @@ -40,14 +42,6 @@ public Document publishDocument(Document doc) { return doc; } - @Override - public void addDocumentListener(DocumentListener listener) { - // TODO Auto-generated method stub - // ?? - // subscribe("publishDocument", topicMethod, callbackName, callbackMethod); - - } - @Override public ProcessingStatus onDocument(Document doc) { // TODO Auto-generated method stub @@ -101,9 +95,16 @@ public void flush() { public static void main(String[] args) throws Exception { - // create the pipeline service in MRL - DocumentPipeline pipeline = (DocumentPipeline) Runtime.start("docproc", "DocumentPipeline"); + LoggingFactory.init(Level.INFO); + + WebGui webgui = (WebGui)Runtime.start("webgui", "WebGui"); + // start embedded solr + Solr solr = (Solr)Runtime.start("solr","Solr"); + solr.startEmbedded(); + + // start the pipeline to process the files from the file system + DocumentPipeline pipeline = (DocumentPipeline) Runtime.start("docproc", "DocumentPipeline"); // pipeline.workflowName = "default"; // create a workflow to load into that pipeline service WorkflowConfiguration workflowConfig = new WorkflowConfiguration("default"); @@ -111,22 +112,36 @@ public static void main(String[] args) throws Exception { StageConfiguration stage1Config = new StageConfiguration(); stage1Config.setStageClass("org.myrobotlab.document.transformer.SetStaticFieldValue"); stage1Config.setStageName("SetTableField"); - stage1Config.setStringParam("table", "MRL"); + stage1Config.setStringParam("type", "file"); workflowConfig.addStage(stage1Config); StageConfiguration stage2Config = new StageConfiguration(); - stage2Config.setStageClass("org.myrobotlab.document.transformer.SendToSolr"); - stage2Config.setStageName("SendToSolr"); - stage2Config.setStringParam("solrUrl", "http://phobos:8983/solr/graph"); + stage2Config.setStageClass("org.myrobotlab.document.transformer.TextExtractor"); + stage2Config.setStageName("TextExtractor"); workflowConfig.addStage(stage2Config); + + // StageConfiguration stage2Config = new StageConfiguration(); + // stage2Config.setStageClass("org.myrobotlab.document.transformer.SendToSolr"); + // stage2Config.setStageName("SendToSolr"); + // stage2Config.setStringParam("solrUrl", "http://phobos:8983/solr/graph"); + // workflowConfig.addStage(stage2Config); pipeline.setConfig(workflowConfig); pipeline.initalize(); - RSSConnector connector = (RSSConnector) Runtime.start("rss", "RSSConnector"); - connector.addDocumentListener(pipeline); - connector.startCrawling(); + // attach the pipeline to solr. + pipeline.attachDocumentListener(solr.getName()); + + // start the file connector to scan the file system. + // RSSConnector connector = (RSSConnector) Runtime.start("rss", "RSSConnector"); + FileConnector connector = (FileConnector) Runtime.start("fileconnector", "FileConnector"); + connector.setDirectory("D:\\Music"); + // connector to pipeline connection + connector.attachDocumentListener(pipeline.getName()); + + // start the crawl! + connector.startCrawling(); // TODO: make sure we flush the pending batches! // connector.flush(); // poll to make sure the connector is still running./ @@ -137,14 +152,14 @@ public static void main(String[] args) throws Exception { // when the connector is done, tell the pipeline to flush/ pipeline.flush(); - // wee! news! + // } public void initalize() throws ClassNotFoundException { // init the workflow server and load the pipeline config. if (workflowServer == null) { - workflowServer = WorkflowServer.getInstance(); + workflowServer = WorkflowServer.getInstance(this); } workflowServer.addWorkflow(workFlowConfig); workflowName = workFlowConfig.getName(); diff --git a/src/main/java/org/myrobotlab/service/FileConnector.java b/src/main/java/org/myrobotlab/service/FileConnector.java index 7e755ff027..c4a5a7ffbb 100644 --- a/src/main/java/org/myrobotlab/service/FileConnector.java +++ b/src/main/java/org/myrobotlab/service/FileConnector.java @@ -7,6 +7,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Date; import org.myrobotlab.document.Document; import org.myrobotlab.document.connector.AbstractConnector; @@ -69,10 +70,11 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } String docId = getDocIdPrefix() + file.toFile().getAbsolutePath(); Document doc = new Document(docId); - doc.setField("last_modified", attrs.lastModifiedTime()); - doc.setField("created_date", attrs.creationTime()); - doc.setField("filename", file.toFile().getAbsolutePath()); + doc.setField("last_modified", new Date(attrs.lastModifiedTime().toMillis())); + doc.setField("created_date", new Date(attrs.creationTime().toMillis())); + doc.setField("filepath", file.toFile().getAbsolutePath()); doc.setField("size", attrs.size()); + doc.setField("type", "file"); // TODO: potentially add a byte array of the file // or maybe an input stream or other handle to the file. feed(doc); diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 42502b0649..747557bb33 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -515,6 +515,7 @@ public QueryResponse searchWithFacets(String queryString, int rows, int start, S query.setRows(rows); query.setStart(start); query.setFacet(true); + query.setFacetLimit(10); query.setFacetMinCount(1); // TODO: expose sorting in a fancier search method signature // Alternatively, pass the list of parameters and their values into a generic search method instead. diff --git a/src/main/java/org/myrobotlab/service/interfaces/DocumentListener.java b/src/main/java/org/myrobotlab/service/interfaces/DocumentListener.java index 54570ad011..7c0c9f46b0 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/DocumentListener.java +++ b/src/main/java/org/myrobotlab/service/interfaces/DocumentListener.java @@ -13,6 +13,7 @@ public interface DocumentListener { public ProcessingStatus onDocuments(List docs); + // TODO: maybe remove this from the interface. public boolean onFlush(); } diff --git a/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java b/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java index 9550e29a75..4897123df7 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java +++ b/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java @@ -4,10 +4,20 @@ public interface DocumentPublisher { + public static String[] publishMethods = new String[] { "publishDocument" }; + public String getName(); - + public Document publishDocument(Document doc); + + default public void attachDocumentListener(String name) { + for (String publishMethod : DocumentPublisher.publishMethods) { + addListener(publishMethod, name); + } + } + + // Add the addListener method to the interface all services implement this. + public void addListener(String topicMethod, String callbackName); - public void addDocumentListener(DocumentListener listener); } diff --git a/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java b/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java index 7a1cdfc6bb..3ec1139e97 100644 --- a/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java @@ -16,7 +16,9 @@ public DocumentPipelineMeta() { addDescription("This service will pass a document through a document processing pipeline made up of transformers"); addCategory("ingest"); - addDependency("org.apache.tika", "tika-core", "1.22"); + addDependency("org.apache.tika", "tika-core", "2.8.0"); + addDependency("org.apache.tika", "tika-parser-audiovideo-module", "2.8.0"); + addDependency("org.apache.opennlp", "opennlp-tools", "1.6.0"); addDependency("net.objecthunter", "exp4j", "0.4.8"); // for parsing wikitext diff --git a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js index e70b017891..b3e1a8e856 100644 --- a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js @@ -12,7 +12,7 @@ angular.module('mrlapp.service.SolrGui', []).controller('SolrGuiCtrl', ['$scope' $scope.filters = []; // TODO: maybe some other fields.. // TODO: support range facets - $scope.facetFields = ['type', 'sender_type', 'sender','method']; + $scope.facetFields = ['type', 'xmpdm_artist', 'xmpdm_releasedate', 'xmpdm_genre','sender_type', 'sender','method']; // GOOD TEMPLATE TO FOLLOW this.updateState = function(service) { $scope.service = service diff --git a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html index 150c8a62ea..6bfb9d1344 100644 --- a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html @@ -38,7 +38,7 @@
- Doc: {{$index + startOffset}} + Doc: {{$index + startOffset + 1}} diff --git a/src/test/java/org/myrobotlab/document/connector/WikipediaIndexer.java b/src/test/java/org/myrobotlab/document/connector/WikipediaIndexer.java index c6f813a952..20faa46fdc 100644 --- a/src/test/java/org/myrobotlab/document/connector/WikipediaIndexer.java +++ b/src/test/java/org/myrobotlab/document/connector/WikipediaIndexer.java @@ -72,7 +72,7 @@ public static void main(String[] args) throws ClassNotFoundException { docproc.initalize(); docproc.startService(); // attach the doc proc to the connector - wikipediaConnector.addDocumentListener(docproc); + wikipediaConnector.attachDocumentListener(docproc.getName()); wikipediaConnector.setBatchSize(500); // start crawling... wikipediaConnector.getOutbox().setMaxQueueSize(1); diff --git a/src/test/java/org/myrobotlab/service/HarryTest.java b/src/test/java/org/myrobotlab/service/HarryTest.java index d242e1e672..d08da9ccbd 100755 --- a/src/test/java/org/myrobotlab/service/HarryTest.java +++ b/src/test/java/org/myrobotlab/service/HarryTest.java @@ -62,7 +62,7 @@ private void goLearnStuff(Solr solr, ProgramAB harry) throws InterruptedExceptio String rssUrl = "http://feeds.reuters.com/reuters/scienceNews"; RSSConnector rss = (RSSConnector) Runtime.start("rss", "RSSConnector"); rss.setRssUrl(rssUrl); - rss.addDocumentListener(solr); + rss.attachDocumentListener(solr.getName()); Thread.sleep(1000); diff --git a/src/test/java/org/myrobotlab/service/interfaces/AbstractConnectorTest.java b/src/test/java/org/myrobotlab/service/interfaces/AbstractConnectorTest.java index db2f1b6ebb..ace69e8e18 100644 --- a/src/test/java/org/myrobotlab/service/interfaces/AbstractConnectorTest.java +++ b/src/test/java/org/myrobotlab/service/interfaces/AbstractConnectorTest.java @@ -23,7 +23,7 @@ public void test() { MockDocumentListener listener = createListener(); connector.startService(); listener.startService(); - connector.addDocumentListener(listener); + connector.attachDocumentListener(listener.getName()); connector.startCrawling(); // flush any current batch and wait until the outbox is empty. connector.flush(); From f87e8cfe0ca64e51ccd47c3fff30e2e87c0ae00c Mon Sep 17 00:00:00 2001 From: kwatters Date: Wed, 14 Jun 2023 12:59:31 -0400 Subject: [PATCH 02/54] log errors better in the text extractor --- .../document/transformer/TextExtractor.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java index 06810fd420..add7759604 100644 --- a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java +++ b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java @@ -87,15 +87,14 @@ public List processDocument(Document doc) { BodyContentHandler bch = new BodyContentHandler(textData); try { parser.parse(binaryData, bch, metadata, parseCtx); - } catch (IOException e) { + //} catch (IOException e) { // TODO Auto-generated catch block - e.printStackTrace(); - } catch (TikaException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (SAXException e) { + // e.printStackTrace(); + } catch (TikaException|IOException|SAXException e) { // TODO Auto-generated catch block e.printStackTrace(); + log.warn("Error processing {} :", doc.getId(), e); + doc.setField("error", e); } doc.addToField(textField, textData.toString()); From 1ad0bed7f712308763694bf364072bf45cff08bc Mon Sep 17 00:00:00 2001 From: kwatters Date: Wed, 14 Jun 2023 14:54:32 -0400 Subject: [PATCH 03/54] continue processing in the event of an error. --- .../document/transformer/TextExtractor.java | 4 ++-- .../org/myrobotlab/service/DocumentPipeline.java | 5 ++++- .../java/org/myrobotlab/service/FileConnector.java | 13 ++++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java index add7759604..4c0888fc06 100644 --- a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java +++ b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java @@ -8,6 +8,7 @@ import java.util.List; import org.apache.tika.exception.TikaException; +import org.apache.tika.exception.ZeroByteFileException; import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.ParseContext; @@ -75,9 +76,8 @@ public List processDocument(Document doc) { try { binaryData = new FileInputStream(f); } catch (FileNotFoundException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); // This should never happen. + log.warn("Document {} not found.", doc.getId(), e1); continue; } // InputStream binaryData = null; diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index 73cd85eca3..f722d6d4fa 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -109,6 +109,8 @@ public static void main(String[] args) throws Exception { // create a workflow to load into that pipeline service WorkflowConfiguration workflowConfig = new WorkflowConfiguration("default"); workflowConfig.setName("default"); + workflowConfig.setNumWorkerThreads(8); + StageConfiguration stage1Config = new StageConfiguration(); stage1Config.setStageClass("org.myrobotlab.document.transformer.SetStaticFieldValue"); stage1Config.setStageName("SetTableField"); @@ -130,12 +132,13 @@ public static void main(String[] args) throws Exception { pipeline.initalize(); // attach the pipeline to solr. + // pipeline.attachDocumentListener(solr.getName()); // start the file connector to scan the file system. // RSSConnector connector = (RSSConnector) Runtime.start("rss", "RSSConnector"); FileConnector connector = (FileConnector) Runtime.start("fileconnector", "FileConnector"); - connector.setDirectory("D:\\Music"); + connector.setDirectory("Z:\\Music"); // connector to pipeline connection connector.attachDocumentListener(pipeline.getName()); diff --git a/src/main/java/org/myrobotlab/service/FileConnector.java b/src/main/java/org/myrobotlab/service/FileConnector.java index c4a5a7ffbb..9e4a093b09 100644 --- a/src/main/java/org/myrobotlab/service/FileConnector.java +++ b/src/main/java/org/myrobotlab/service/FileConnector.java @@ -83,7 +83,18 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { - throw exc; + // throw exc; + String docId = getDocIdPrefix() + file.toFile().getAbsolutePath(); + Document doc = new Document(docId); + doc.setField("type", "file"); + // TODO: how does this serialize? + doc.setField("error", exc); + // doc.setField("timestamp", new Date()); + feed(doc); + log.warn("Exception processing {}", file, exc); + + // Keep going!!! + return FileVisitResult.CONTINUE; } @Override From 1607be2f5414b2ad89ba58731b2e9bff8219eb0a Mon Sep 17 00:00:00 2001 From: kwatters Date: Wed, 14 Jun 2023 17:19:11 -0400 Subject: [PATCH 04/54] click on the song filename and it will start playing in audiofile service... --- .../java/org/myrobotlab/service/DocumentPipeline.java | 8 +++++++- src/main/java/org/myrobotlab/service/Solr.java | 3 ++- .../resource/WebGui/app/service/js/SolrGui.js | 10 ++++++++++ .../resource/WebGui/app/service/views/SolrGui.html | 3 ++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index f722d6d4fa..fee871ea25 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -97,6 +97,9 @@ public static void main(String[] args) throws Exception { LoggingFactory.init(Level.INFO); + + AudioFile audiofile = (AudioFile)Runtime.start("audiofile", "AudioFile"); + WebGui webgui = (WebGui)Runtime.start("webgui", "WebGui"); // start embedded solr @@ -144,7 +147,10 @@ public static void main(String[] args) throws Exception { connector.attachDocumentListener(pipeline.getName()); // start the crawl! - connector.startCrawling(); + boolean doCrawl = false; + if (doCrawl) { + connector.startCrawling(); + } // TODO: make sure we flush the pending batches! // connector.flush(); // poll to make sure the connector is still running./ diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 747557bb33..dd0614c18c 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -510,12 +510,13 @@ public QueryResponse search(String queryString, int rows, int start, boolean mos */ public QueryResponse searchWithFacets(String queryString, int rows, int start, String[] facetFields, String[] filters) { log.info("Searching for (with facets): {}", queryString); + int numFacetBuckets = 20; SolrQuery query = new SolrQuery(); query.set("q", queryString); query.setRows(rows); query.setStart(start); query.setFacet(true); - query.setFacetLimit(10); + query.setFacetLimit(numFacetBuckets); query.setFacetMinCount(1); // TODO: expose sorting in a fancier search method signature // Alternatively, pass the list of parameters and their values into a generic search method instead. diff --git a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js index b3e1a8e856..60284c4e05 100644 --- a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js @@ -94,6 +94,16 @@ angular.module('mrlapp.service.SolrGui', []).controller('SolrGuiCtrl', ['$scope' $scope.execSearch(); } + $scope.playFile = function(filepath) { + // stop the audiofile if it's currently playing. + mrl.sendTo("audiofile", "stop"); + // start the new song. + mrl.sendTo("audiofile", "play", filepath[0]); + + // mrl.sendTo("foobar", "play", filepath[0]); + // mrl.sendTo("solr", "play", filepath[0]); + } + msg.subscribe('publishResults'); msg.subscribe(this); // mrl.subscribe($scope.service.name, 'publishResults', $scope.service.results); diff --git a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html index 6bfb9d1344..0b8fc75058 100644 --- a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html @@ -43,7 +43,8 @@ - + +
{{key}}
{{key}} {{value}}{{value}}{{value}}
From 22df3d60623ccb3ad78a083097c7f270a83daec5 Mon Sep 17 00:00:00 2001 From: kwatters Date: Wed, 14 Jun 2023 17:43:56 -0400 Subject: [PATCH 05/54] minimal webgui for FileConnector --- .../WebGui/app/service/js/FileConnectorGui.js | 65 +++++++++++++++++++ .../app/service/views/FileConnectorGui.html | 13 ++++ 2 files changed, 78 insertions(+) create mode 100755 src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js create mode 100755 src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html diff --git a/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js b/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js new file mode 100755 index 0000000000..48fb5f54ca --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js @@ -0,0 +1,65 @@ +angular.module('mrlapp.service.FileConnectorGui', []).controller('FileConnectorGuiCtrl', ['$scope', 'mrl', function($scope, mrl) { + console.info('FileConnectorGuiCtrl'); + var _self = this + var msg = this.msg + + $scope.filepath = 'Z:\\Music' + $scope.document = ''; +// // TODO: something useful?! +// $scope.solrResults = ''; +// $scope.queryString = '*:*'; +// $scope.startOffset = 0; +// $scope.endOffset = 0; +// $scope.numFound = 0; +// $scope.pageSize = 20; +// $scope.filters = []; +// // TODO: maybe some other fields.. +// // TODO: support range facets +// $scope.facetFields = ['type', 'xmpdm_artist', 'xmpdm_releasedate', 'xmpdm_genre','sender_type', 'sender','method']; + // GOOD TEMPLATE TO FOLLOW + this.updateState = function(service) { + $scope.service = service + } + + this.onMsg = function(inMsg) { + let data = inMsg.data[0] + switch (inMsg.method) { + case 'onDocument': + //var solrResults = JSON.parse(data); + console.info("On Document!"); + $scope.document = data; + //console.info(solrResults); + //$scope.solrResults = solrResults; + // set the start/end offsets perhaps? + //$scope.numFound = solrResults.numFound; + // TODO: this is conflated logic. + // $scope.startOffset = solrResults.start + //$scope.endOffset = solrResults.size + solrResults.start + $scope.$apply(); + break + case 'onState': + _self.updateState(data) + $scope.$apply() + break + case 'onStatus': + $scope.status = data; + $scope.$apply() + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + + }; + + $scope.startCrawling = function(filepath) { + mrl.sendTo($scope.service.name, "startCrawling"); + } + + // TODO ? I don't think we want to subscribe to publishDocument.. + msg.subscribe('publishDocument'); + msg.subscribe(this); + // mrl.subscribe($scope.service.name, 'publishResults', $scope.service.results); + // $scope.panel.initDone(); + + }]); \ No newline at end of file diff --git a/src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html b/src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html new file mode 100755 index 0000000000..f453090666 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html @@ -0,0 +1,13 @@ +
+ File Traverser! this service can scan a file system and publish documents to other services. +
+
+ + +
+ +

Document!

+
+ {{document}} +
+
From 1cb57398f7c634ab1f779eca47234cac6f27d747 Mon Sep 17 00:00:00 2001 From: kwatters Date: Wed, 14 Jun 2023 17:51:35 -0400 Subject: [PATCH 06/54] now with more worky... --- .../WebGui/app/service/js/FileConnectorGui.js | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js b/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js index 48fb5f54ca..3f54cfb3dc 100755 --- a/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js @@ -3,19 +3,9 @@ angular.module('mrlapp.service.FileConnectorGui', []).controller('FileConnectorG var _self = this var msg = this.msg - $scope.filepath = 'Z:\\Music' - $scope.document = ''; -// // TODO: something useful?! -// $scope.solrResults = ''; -// $scope.queryString = '*:*'; -// $scope.startOffset = 0; -// $scope.endOffset = 0; -// $scope.numFound = 0; -// $scope.pageSize = 20; -// $scope.filters = []; -// // TODO: maybe some other fields.. -// // TODO: support range facets -// $scope.facetFields = ['type', 'xmpdm_artist', 'xmpdm_releasedate', 'xmpdm_genre','sender_type', 'sender','method']; + $scope.filepath = ''; + $scope.document = ''; + // GOOD TEMPLATE TO FOLLOW this.updateState = function(service) { $scope.service = service @@ -25,16 +15,8 @@ angular.module('mrlapp.service.FileConnectorGui', []).controller('FileConnectorG let data = inMsg.data[0] switch (inMsg.method) { case 'onDocument': - //var solrResults = JSON.parse(data); console.info("On Document!"); $scope.document = data; - //console.info(solrResults); - //$scope.solrResults = solrResults; - // set the start/end offsets perhaps? - //$scope.numFound = solrResults.numFound; - // TODO: this is conflated logic. - // $scope.startOffset = solrResults.start - //$scope.endOffset = solrResults.size + solrResults.start $scope.$apply(); break case 'onState': @@ -53,13 +35,12 @@ angular.module('mrlapp.service.FileConnectorGui', []).controller('FileConnectorG }; $scope.startCrawling = function(filepath) { + mrl.sendTo($scope.service.name, "setDirectory", $scope.filepath) mrl.sendTo($scope.service.name, "startCrawling"); } - // TODO ? I don't think we want to subscribe to publishDocument.. + // This could result in a lot of data getting returned to the webgui.. we'll see. msg.subscribe('publishDocument'); msg.subscribe(this); - // mrl.subscribe($scope.service.name, 'publishResults', $scope.service.results); - // $scope.panel.initDone(); }]); \ No newline at end of file From 07084cf2909859d114d2672ea1a4265f9ebc8579 Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 15 Jun 2023 16:35:06 -0400 Subject: [PATCH 07/54] some cleanup. adding a document pipeline gui, some relevancy tuning --- .../org/myrobotlab/document/Document.java | 9 +++++ .../document/transformer/TextExtractor.java | 12 +++++- .../org/myrobotlab/service/FileConnector.java | 2 +- .../java/org/myrobotlab/service/Solr.java | 10 +++++ .../app/service/js/DocumentPipelineGui.js | 40 +++++++++++++++++++ .../WebGui/app/service/js/FileConnectorGui.js | 7 +++- .../service/views/DocumentPipelineGui.html | 35 ++++++++++++++++ .../app/service/views/FileConnectorGui.html | 1 + 8 files changed, 112 insertions(+), 4 deletions(-) create mode 100755 src/main/resources/resource/WebGui/app/service/js/DocumentPipelineGui.js create mode 100755 src/main/resources/resource/WebGui/app/service/views/DocumentPipelineGui.html diff --git a/src/main/java/org/myrobotlab/document/Document.java b/src/main/java/org/myrobotlab/document/Document.java index fbdf020c8d..02332691e8 100644 --- a/src/main/java/org/myrobotlab/document/Document.java +++ b/src/main/java/org/myrobotlab/document/Document.java @@ -35,6 +35,9 @@ public ArrayList getField(String fieldName) { } public void setField(String fieldName, ArrayList value) { + if (fieldName == null) { + return; + } if (value == null) { data.remove(fieldName); } else { @@ -43,6 +46,9 @@ public void setField(String fieldName, ArrayList value) { } public void setField(String fieldName, Object value) { + if (fieldName == null) { + return; + } // set field overwrites existing values in the field. if (value == null) { data.remove(fieldName); @@ -77,6 +83,9 @@ public void renameField(String oldField, String newField) { } public void addToField(String fieldName, Object value) { + if (fieldName == null) { + return; + } if (data.containsKey(fieldName) && (data.get(fieldName) != null)) { data.get(fieldName).add(value); } else { diff --git a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java index 4c0888fc06..9e477f1797 100644 --- a/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java +++ b/src/main/java/org/myrobotlab/document/transformer/TextExtractor.java @@ -72,6 +72,12 @@ public List processDocument(Document doc) { continue; } + if (f.length() == 0) { + // TODO: this is a zero byte file. + log.info("zero byte file {}", f.getAbsolutePath()); + continue; + } + FileInputStream binaryData = null; try { binaryData = new FileInputStream(f); @@ -101,8 +107,10 @@ public List processDocument(Document doc) { for (String name : metadata.names()) { // clean the field name first. String cleanName = cleanFieldName(name); - for (String value : metadata.getValues(name)) { - doc.addToField(cleanName, value); + if (cleanName != null) { + for (String value : metadata.getValues(name)) { + doc.addToField(cleanName, value); + } } } } diff --git a/src/main/java/org/myrobotlab/service/FileConnector.java b/src/main/java/org/myrobotlab/service/FileConnector.java index 9e4a093b09..643c2f5e6c 100644 --- a/src/main/java/org/myrobotlab/service/FileConnector.java +++ b/src/main/java/org/myrobotlab/service/FileConnector.java @@ -24,7 +24,7 @@ public class FileConnector extends AbstractConnector implements DocumentPublishe private String directory; // TODO: add wildcard includes/excludes // TODO: add file path includes/excludes - private boolean interrupted = false; + private volatile boolean interrupted = false; public FileConnector(String name, String id) { super(name, id); diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index dd0614c18c..f553977ace 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -518,6 +518,14 @@ public QueryResponse searchWithFacets(String queryString, int rows, int start, S query.setFacet(true); query.setFacetLimit(numFacetBuckets); query.setFacetMinCount(1); + + query.add("qf", "dc_title"); + query.add("qf", "xmpdm_artist_txt_en"); + query.add("qf", "xmpdm_releasedate"); + query.add("qf", "filepath_txt_en"); + query.add("qf", "xmpdm_album_txt_en"); + query.setParam("defType", "edismax"); + query.setParam("q.op", "AND"); // TODO: expose sorting in a fancier search method signature // Alternatively, pass the list of parameters and their values into a generic search method instead. query.setSort("index_date", ORDER.desc); @@ -647,6 +655,8 @@ public ProcessingStatus onDocument(Document doc) { // always be batching when sending docs. ArrayList docs = new ArrayList(); docs.add(doc); + // TODO: we want to add to the current batch to send.. + // and make sure we have a thread flushing the batch if it gets too old. return onDocuments(docs); } diff --git a/src/main/resources/resource/WebGui/app/service/js/DocumentPipelineGui.js b/src/main/resources/resource/WebGui/app/service/js/DocumentPipelineGui.js new file mode 100755 index 0000000000..889f743a67 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/DocumentPipelineGui.js @@ -0,0 +1,40 @@ +angular.module('mrlapp.service.DocumentPipelineGui', []).controller('DocumentPipelineGuiCtrl', ['$scope', 'mrl', function($scope, mrl) { + console.info('FileConnectorGuiCtrl'); + var _self = this + var msg = this.msg + + $scope.document = ''; + + // GOOD TEMPLATE TO FOLLOW + this.updateState = function(service) { + $scope.service = service + } + + this.onMsg = function(inMsg) { + let data = inMsg.data[0] + switch (inMsg.method) { + case 'onDocument': + console.info("On Document!"); + $scope.document = data; + $scope.$apply(); + break + case 'onState': + _self.updateState(data) + $scope.$apply() + break + case 'onStatus': + $scope.status = data; + $scope.$apply() + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + + }; + + // This could result in a lot of data getting returned to the webgui.. we'll see. + // msg.subscribe('publishDocument'); + msg.subscribe(this); + + }]); \ No newline at end of file diff --git a/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js b/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js index 3f54cfb3dc..ed7cae6f32 100755 --- a/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/FileConnectorGui.js @@ -38,9 +38,14 @@ angular.module('mrlapp.service.FileConnectorGui', []).controller('FileConnectorG mrl.sendTo($scope.service.name, "setDirectory", $scope.filepath) mrl.sendTo($scope.service.name, "startCrawling"); } + + $scope.stopCrawling = function(filepath) { + // TODO: this doesn't seem to work. + mrl.sendTo($scope.service.name, "stopCrawling"); + } // This could result in a lot of data getting returned to the webgui.. we'll see. - msg.subscribe('publishDocument'); + // msg.subscribe('publishDocument'); msg.subscribe(this); }]); \ No newline at end of file diff --git a/src/main/resources/resource/WebGui/app/service/views/DocumentPipelineGui.html b/src/main/resources/resource/WebGui/app/service/views/DocumentPipelineGui.html new file mode 100755 index 0000000000..48aee1f039 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/DocumentPipelineGui.html @@ -0,0 +1,35 @@ +
+ Document Pipeline! + +

Workflow Config

+
+ Name : {{service.workFlowConfig.name}} +
+ Number of Threads : {{service.workFlowConfig.numWorkerThreads}} +
+

Stages

+ + + + + +
+
  • Stage {{stage.stageName}}
  • +
  • Class {{stage.stageClass}}
  • +
    + + + + +
    {{key}}{{value}}
    +
    +
    + +
    +

    Document!

    +
    + {{document}} +
    + + +
    diff --git a/src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html b/src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html index f453090666..3588ca0382 100755 --- a/src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/FileConnectorGui.html @@ -4,6 +4,7 @@
    +

    Document!

    From 8809385c638824f06ecbcb8ec0620db1c7324183 Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 15 Jun 2023 16:36:19 -0400 Subject: [PATCH 08/54] foo --- src/main/java/org/myrobotlab/service/Solr.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index f553977ace..678457d5c2 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -524,6 +524,7 @@ public QueryResponse searchWithFacets(String queryString, int rows, int start, S query.add("qf", "xmpdm_releasedate"); query.add("qf", "filepath_txt_en"); query.add("qf", "xmpdm_album_txt_en"); + query.add("qf", "xmpdm_genre_txt_en"); query.setParam("defType", "edismax"); query.setParam("q.op", "AND"); // TODO: expose sorting in a fancier search method signature From eee3aa06868d4ab46db92ecdcb1d4bb4b79d7153 Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 15 Jun 2023 16:45:35 -0400 Subject: [PATCH 09/54] small log message so you know what file the audiofile service is starting to play. --- src/main/java/org/myrobotlab/service/AudioFile.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/myrobotlab/service/AudioFile.java b/src/main/java/org/myrobotlab/service/AudioFile.java index 3738338a69..cbf064bf30 100644 --- a/src/main/java/org/myrobotlab/service/AudioFile.java +++ b/src/main/java/org/myrobotlab/service/AudioFile.java @@ -165,6 +165,7 @@ public void stopService() { } public AudioData play(String filename) { + log.info("Audio file playing {}", filename); return play(filename, false); } From 70acacb7333e1238ce46b85b9b3977411c51b971 Mon Sep 17 00:00:00 2001 From: kwatters Date: Fri, 16 Jun 2023 16:29:34 -0400 Subject: [PATCH 10/54] adding some config suppport for fileconnector, documentpipeline, and solr --- .../myrobotlab/service/DocumentPipeline.java | 30 +++++++++++++++---- .../org/myrobotlab/service/FileConnector.java | 24 ++++++++++++--- .../java/org/myrobotlab/service/Solr.java | 21 +++++++++++++ .../config/DocumentPipelineConfig.java | 9 ++++++ .../service/config/FileConnectorConfig.java | 7 +++++ .../myrobotlab/service/config/SolrConfig.java | 6 ++++ 6 files changed, 87 insertions(+), 10 deletions(-) create mode 100755 src/main/java/org/myrobotlab/service/config/DocumentPipelineConfig.java create mode 100755 src/main/java/org/myrobotlab/service/config/FileConnectorConfig.java create mode 100755 src/main/java/org/myrobotlab/service/config/SolrConfig.java diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index fee871ea25..502c53305d 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -12,6 +12,8 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.DocumentPipelineConfig; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.DocumentListener; import org.myrobotlab.service.interfaces.DocumentPublisher; @@ -99,7 +101,6 @@ public static void main(String[] args) throws Exception { AudioFile audiofile = (AudioFile)Runtime.start("audiofile", "AudioFile"); - WebGui webgui = (WebGui)Runtime.start("webgui", "WebGui"); // start embedded solr @@ -112,11 +113,11 @@ public static void main(String[] args) throws Exception { // create a workflow to load into that pipeline service WorkflowConfiguration workflowConfig = new WorkflowConfiguration("default"); workflowConfig.setName("default"); - workflowConfig.setNumWorkerThreads(8); + workflowConfig.setNumWorkerThreads(16); StageConfiguration stage1Config = new StageConfiguration(); stage1Config.setStageClass("org.myrobotlab.document.transformer.SetStaticFieldValue"); - stage1Config.setStageName("SetTableField"); + stage1Config.setStageName("SetTypeField"); stage1Config.setStringParam("type", "file"); workflowConfig.addStage(stage1Config); @@ -125,23 +126,25 @@ public static void main(String[] args) throws Exception { stage2Config.setStageName("TextExtractor"); workflowConfig.addStage(stage2Config); - + // TODO: rename fields.. + // TODO: delete unnecessary fields. // StageConfiguration stage2Config = new StageConfiguration(); // stage2Config.setStageClass("org.myrobotlab.document.transformer.SendToSolr"); // stage2Config.setStageName("SendToSolr"); // stage2Config.setStringParam("solrUrl", "http://phobos:8983/solr/graph"); // workflowConfig.addStage(stage2Config); + pipeline.setConfig(workflowConfig); pipeline.initalize(); // attach the pipeline to solr. // - pipeline.attachDocumentListener(solr.getName()); + // pipeline.attachDocumentListener(solr.getName()); // start the file connector to scan the file system. // RSSConnector connector = (RSSConnector) Runtime.start("rss", "RSSConnector"); FileConnector connector = (FileConnector) Runtime.start("fileconnector", "FileConnector"); - connector.setDirectory("Z:\\Music"); + // connector to pipeline connection connector.attachDocumentListener(pipeline.getName()); @@ -149,6 +152,7 @@ public static void main(String[] args) throws Exception { // start the crawl! boolean doCrawl = false; if (doCrawl) { + connector.setDirectory("Z:\\Movies"); connector.startCrawling(); } // TODO: make sure we flush the pending batches! @@ -199,4 +203,18 @@ public boolean onFlush() { return true; } + @Override + public ServiceConfig apply(ServiceConfig inConfig) { + DocumentPipelineConfig config = (DocumentPipelineConfig)super.apply(inConfig); + // TODO Auto-generated method stub + this.workFlowConfig = config.workFlowConfig; + return config; + } + + @Override + public ServiceConfig getConfig() { + // return the config + return config; + } + } diff --git a/src/main/java/org/myrobotlab/service/FileConnector.java b/src/main/java/org/myrobotlab/service/FileConnector.java index 643c2f5e6c..4163b04e40 100644 --- a/src/main/java/org/myrobotlab/service/FileConnector.java +++ b/src/main/java/org/myrobotlab/service/FileConnector.java @@ -14,6 +14,8 @@ import org.myrobotlab.document.connector.ConnectorState; import org.myrobotlab.document.transformer.ConnectorConfig; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.config.FileConnectorConfig; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.DocumentPublisher; import org.slf4j.Logger; @@ -21,7 +23,8 @@ public class FileConnector extends AbstractConnector implements DocumentPublishe public final static Logger log = LoggerFactory.getLogger(FileConnector.class.getCanonicalName()); private static final long serialVersionUID = 1L; - private String directory; + // private String directory; + private FileConnectorConfig config; // TODO: add wildcard includes/excludes // TODO: add file path includes/excludes private volatile boolean interrupted = false; @@ -39,7 +42,7 @@ public void setConfig(ConnectorConfig config) { @Override public void startCrawling() { state = ConnectorState.RUNNING; - Path startPath = Paths.get(directory); + Path startPath = Paths.get(config.directory); try { Files.walkFileTree(startPath, this); } catch (IOException e) { @@ -107,11 +110,24 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } public String getDirectory() { - return directory; + return config.directory; } public void setDirectory(String directory) { - this.directory = directory; + config.directory = directory; } + @Override + public ServiceConfig apply(ServiceConfig inConfig) { + // + FileConnectorConfig config = (FileConnectorConfig)super.apply(inConfig); + config.directory = config.directory; + return config; + } + + @Override + public ServiceConfig getConfig() { + // return the config + return config; + } } diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 678457d5c2..ec71c335cb 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -25,6 +25,7 @@ import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.core.CoreContainer; + import org.bytedeco.opencv.opencv_core.IplImage; import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.document.Document; @@ -44,6 +45,8 @@ import org.myrobotlab.opencv.OpenCVData; import org.myrobotlab.opencv.YoloDetectedObject; import org.myrobotlab.programab.Response; +import org.myrobotlab.service.config.ServiceConfig; +import org.myrobotlab.service.config.SolrConfig; import org.myrobotlab.service.interfaces.DocumentListener; import org.myrobotlab.service.interfaces.SpeechRecognizer; import org.myrobotlab.service.interfaces.TextListener; @@ -65,6 +68,7 @@ */ public class Solr extends Service implements DocumentListener, TextListener, MessageListener { + private SolrConfig config; private static final String CORE_NAME = "core1"; public final static Logger log = LoggerFactory.getLogger(Solr.class); private static final long serialVersionUID = 1L; @@ -1154,4 +1158,21 @@ public void releaseService() { super.releaseService(); } + @Override + public ServiceConfig apply(ServiceConfig inConfig) { + // + this.config = (SolrConfig)super.apply(inConfig); + + return config; + } + + @Override + public ServiceConfig getConfig() { + // return our config + return config; + } + + + // Config support + } diff --git a/src/main/java/org/myrobotlab/service/config/DocumentPipelineConfig.java b/src/main/java/org/myrobotlab/service/config/DocumentPipelineConfig.java new file mode 100755 index 0000000000..17a42ae982 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/DocumentPipelineConfig.java @@ -0,0 +1,9 @@ +package org.myrobotlab.service.config; + +import org.myrobotlab.document.transformer.WorkflowConfiguration; + +public class DocumentPipelineConfig extends ServiceConfig { + + public WorkflowConfiguration workFlowConfig; + +} diff --git a/src/main/java/org/myrobotlab/service/config/FileConnectorConfig.java b/src/main/java/org/myrobotlab/service/config/FileConnectorConfig.java new file mode 100755 index 0000000000..68b71fad77 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/FileConnectorConfig.java @@ -0,0 +1,7 @@ +package org.myrobotlab.service.config; + +public class FileConnectorConfig extends ServiceConfig { + + public String directory; + +} diff --git a/src/main/java/org/myrobotlab/service/config/SolrConfig.java b/src/main/java/org/myrobotlab/service/config/SolrConfig.java new file mode 100755 index 0000000000..ceedb58fad --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/SolrConfig.java @@ -0,0 +1,6 @@ +package org.myrobotlab.service.config; + +public class SolrConfig extends ServiceConfig { + + // TODO: solr config... +} From 999b2b922c3b88a0dd4981eb028f700711eeec5c Mon Sep 17 00:00:00 2001 From: kwatters Date: Fri, 16 Jun 2023 18:00:06 -0400 Subject: [PATCH 11/54] some work on fileconnector and documentpipeline config files. --- .../myrobotlab/document/transformer/Configuration.java | 9 +++++++-- .../document/transformer/WorkflowConfiguration.java | 8 ++++++-- .../java/org/myrobotlab/service/DocumentPipeline.java | 9 ++++++--- .../java/org/myrobotlab/service/FileConnector.java | 10 +++++++--- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/myrobotlab/document/transformer/Configuration.java b/src/main/java/org/myrobotlab/document/transformer/Configuration.java index 69d273c448..e95e33026a 100644 --- a/src/main/java/org/myrobotlab/document/transformer/Configuration.java +++ b/src/main/java/org/myrobotlab/document/transformer/Configuration.java @@ -1,5 +1,6 @@ package org.myrobotlab.document.transformer; +import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -7,7 +8,7 @@ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.StaxDriver; -public class Configuration { +public class Configuration implements Serializable { // TODO: add a map type. // TODO: push the name/class onto this ? @@ -17,7 +18,11 @@ public class Configuration { // workflow /pipeline config // connector config - protected HashMap config = null; + /** + * + */ + private static final long serialVersionUID = 1L; + public HashMap config = null; // private XStream xstream = null; public Configuration() { diff --git a/src/main/java/org/myrobotlab/document/transformer/WorkflowConfiguration.java b/src/main/java/org/myrobotlab/document/transformer/WorkflowConfiguration.java index 84f6c8dc21..1a1263a1f1 100644 --- a/src/main/java/org/myrobotlab/document/transformer/WorkflowConfiguration.java +++ b/src/main/java/org/myrobotlab/document/transformer/WorkflowConfiguration.java @@ -7,11 +7,15 @@ public class WorkflowConfiguration extends Configuration { - ArrayList stages; + public ArrayList stages; private String name = "default"; private int numWorkerThreads = 1; private int queueLength = 50; - + + public WorkflowConfiguration() { + // needed for yaml config serialization + } + public WorkflowConfiguration(String name) { this.name = name; stages = new ArrayList(); diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index 502c53305d..bf19a98286 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -139,20 +139,21 @@ public static void main(String[] args) throws Exception { // attach the pipeline to solr. // - // pipeline.attachDocumentListener(solr.getName()); + pipeline.attachDocumentListener(solr.getName()); // start the file connector to scan the file system. // RSSConnector connector = (RSSConnector) Runtime.start("rss", "RSSConnector"); FileConnector connector = (FileConnector) Runtime.start("fileconnector", "FileConnector"); - + connector.setDirectory("Z:\\Movies"); // connector to pipeline connection connector.attachDocumentListener(pipeline.getName()); + Runtime.saveConfig("musicsearch"); // start the crawl! boolean doCrawl = false; if (doCrawl) { - connector.setDirectory("Z:\\Movies"); + connector.startCrawling(); } // TODO: make sure we flush the pending batches! @@ -214,6 +215,8 @@ public ServiceConfig apply(ServiceConfig inConfig) { @Override public ServiceConfig getConfig() { // return the config + DocumentPipelineConfig config = (DocumentPipelineConfig)super.getConfig(); + config.workFlowConfig = this.workFlowConfig; return config; } diff --git a/src/main/java/org/myrobotlab/service/FileConnector.java b/src/main/java/org/myrobotlab/service/FileConnector.java index 4163b04e40..b4826f4517 100644 --- a/src/main/java/org/myrobotlab/service/FileConnector.java +++ b/src/main/java/org/myrobotlab/service/FileConnector.java @@ -24,7 +24,7 @@ public class FileConnector extends AbstractConnector implements DocumentPublishe public final static Logger log = LoggerFactory.getLogger(FileConnector.class.getCanonicalName()); private static final long serialVersionUID = 1L; // private String directory; - private FileConnectorConfig config; + private FileConnectorConfig config = new FileConnectorConfig(); // TODO: add wildcard includes/excludes // TODO: add file path includes/excludes private volatile boolean interrupted = false; @@ -42,7 +42,7 @@ public void setConfig(ConnectorConfig config) { @Override public void startCrawling() { state = ConnectorState.RUNNING; - Path startPath = Paths.get(config.directory); + Path startPath = Paths.get(((FileConnectorConfig)config).directory); try { Files.walkFileTree(startPath, this); } catch (IOException e) { @@ -121,13 +121,17 @@ public void setDirectory(String directory) { public ServiceConfig apply(ServiceConfig inConfig) { // FileConnectorConfig config = (FileConnectorConfig)super.apply(inConfig); - config.directory = config.directory; + // anything else? return config; } @Override public ServiceConfig getConfig() { // return the config + // we need the super stuff here. + FileConnectorConfig config = (FileConnectorConfig)super.getConfig(); + // this is goofy.. + config.directory = this.config.directory; return config; } } From 7d8881e1e015d6ece1268d5c318c8682ef37ef09 Mon Sep 17 00:00:00 2001 From: kwatters Date: Sun, 18 Jun 2023 12:10:56 -0400 Subject: [PATCH 12/54] wire through publish flush and some solr config --- .../document/workflow/WorkflowServer.java | 5 +++-- .../myrobotlab/service/DocumentPipeline.java | 5 +++++ .../java/org/myrobotlab/service/Solr.java | 22 ++++++++++++++++++- .../myrobotlab/service/config/SolrConfig.java | 7 +++++- .../service/interfaces/DocumentPublisher.java | 5 +++-- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java b/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java index 8c12595035..c3a0ad05d3 100644 --- a/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java +++ b/src/main/java/org/myrobotlab/document/workflow/WorkflowServer.java @@ -50,10 +50,11 @@ public void processMessage(WorkflowMessage msg) throws InterruptedException { } public void flush(String workflow) { - // TODO Auto-generated method stub + // flush the workflow/pipeline Workflow w = workflowMap.get(workflow); w.flush(); - + // publish that we have flushed (a workflow, pass the flush down the line?) + pipeline.invoke("publishFlush"); } public String[] listWorkflows() { diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index bf19a98286..6785df17bc 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -204,6 +204,11 @@ public boolean onFlush() { return true; } + @Override + public void publishFlush() { + // publish the flush event.. + } + @Override public ServiceConfig apply(ServiceConfig inConfig) { DocumentPipelineConfig config = (DocumentPipelineConfig)super.apply(inConfig); diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index ec71c335cb..655bbb048f 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -1162,13 +1162,33 @@ public void releaseService() { public ServiceConfig apply(ServiceConfig inConfig) { // this.config = (SolrConfig)super.apply(inConfig); - + if (config.embedded) { + // + try { + startEmbedded(); + } catch (SolrServerException | IOException e) { + // TODO: how should we handle this? + log.warn("Error starting embedded solr instance.", e); + e.printStackTrace(); + }; + } return config; } @Override public ServiceConfig getConfig() { // return our config + // we need to create + SolrConfig config = (SolrConfig)super.getConfig(); + // config.embedded = this.embedded + if (this.embeddedSolrServer != null) { + config.embedded = true; + } + + if (this.solrUrl != null) { + config.solrUrl = this.solrUrl; + } + return config; } diff --git a/src/main/java/org/myrobotlab/service/config/SolrConfig.java b/src/main/java/org/myrobotlab/service/config/SolrConfig.java index ceedb58fad..33e574eb90 100755 --- a/src/main/java/org/myrobotlab/service/config/SolrConfig.java +++ b/src/main/java/org/myrobotlab/service/config/SolrConfig.java @@ -2,5 +2,10 @@ public class SolrConfig extends ServiceConfig { - // TODO: solr config... + // if you use embedded, the solrUrl is ignored + public boolean embedded = true; + // If embedded = false, then the following url will be used to search a solr cluster. + // This will/should be replaced with the zkHost and a SolrCloud client. + public String solrUrl = "http://localhost:8983/solr/collection1"; + } diff --git a/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java b/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java index 4897123df7..479dd6dfef 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java +++ b/src/main/java/org/myrobotlab/service/interfaces/DocumentPublisher.java @@ -4,12 +4,14 @@ public interface DocumentPublisher { - public static String[] publishMethods = new String[] { "publishDocument" }; + public static String[] publishMethods = new String[] { "publishDocument" , "publishFlush"}; public String getName(); public Document publishDocument(Document doc); + public void publishFlush(); + default public void attachDocumentListener(String name) { for (String publishMethod : DocumentPublisher.publishMethods) { addListener(publishMethod, name); @@ -19,5 +21,4 @@ default public void attachDocumentListener(String name) { // Add the addListener method to the interface all services implement this. public void addListener(String topicMethod, String callbackName); - } From 6ffbe7cf7db3b3ab2727e60d1aca02b16231d6e1 Mon Sep 17 00:00:00 2001 From: kwatters Date: Mon, 26 Jun 2023 15:15:25 -0400 Subject: [PATCH 13/54] starting to normalize the solr schema --- .../document/transformer/Configuration.java | 6 + .../myrobotlab/service/DocumentPipeline.java | 22 +- .../resource/Solr/core1/conf/managed-schema | 193 ++++++++++-------- .../WebGui/app/service/views/SolrGui.html | 4 +- 4 files changed, 139 insertions(+), 86 deletions(-) diff --git a/src/main/java/org/myrobotlab/document/transformer/Configuration.java b/src/main/java/org/myrobotlab/document/transformer/Configuration.java index e95e33026a..2479c433ee 100644 --- a/src/main/java/org/myrobotlab/document/transformer/Configuration.java +++ b/src/main/java/org/myrobotlab/document/transformer/Configuration.java @@ -137,6 +137,12 @@ public List getListParam(String name) { return null; } + public void setMapProperty(String name, Map map) { + // TODO type safety?! + config.put(name, map); + return; + } + public Map getMapProperty(String name) { // TODO type safety?! return (Map) config.get(name); diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index 6785df17bc..c2edeaf46f 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -1,6 +1,8 @@ package org.myrobotlab.service; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.myrobotlab.document.Document; import org.myrobotlab.document.ProcessingStatus; @@ -105,7 +107,7 @@ public static void main(String[] args) throws Exception { // start embedded solr Solr solr = (Solr)Runtime.start("solr","Solr"); - solr.startEmbedded(); + // solr.startEmbedded(); // start the pipeline to process the files from the file system DocumentPipeline pipeline = (DocumentPipeline) Runtime.start("docproc", "DocumentPipeline"); @@ -126,6 +128,23 @@ public static void main(String[] args) throws Exception { stage2Config.setStageName("TextExtractor"); workflowConfig.addStage(stage2Config); + StageConfiguration stage3Config = new StageConfiguration(); + stage3Config.setStageClass("org.myrobotlab.document.transformer.RenameFields"); + stage3Config.setStageName("RenameFields"); + Map fieldNameMap = new HashMap(); + fieldNameMap.put("xmpdm_tracknumber", "tracknumber"); + fieldNameMap.put("xmpdm_releasedate", "year"); + fieldNameMap.put("xmpdm_duration", "duration"); + fieldNameMap.put("xmpdm_genre", "genre"); + fieldNameMap.put("xmpdm_artist", "artist"); + fieldNameMap.put("dc_title", "title"); + fieldNameMap.put("xmpdm_album", "album"); + + stage3Config.setMapProperty("fieldNameMap", fieldNameMap); + workflowConfig.addStage(stage3Config);; + + //stage3Config. + // TODO: rename fields.. // TODO: delete unnecessary fields. // StageConfiguration stage2Config = new StageConfiguration(); @@ -153,7 +172,6 @@ public static void main(String[] args) throws Exception { // start the crawl! boolean doCrawl = false; if (doCrawl) { - connector.startCrawling(); } // TODO: make sure we flush the pending batches! diff --git a/src/main/resources/resource/Solr/core1/conf/managed-schema b/src/main/resources/resource/Solr/core1/conf/managed-schema index 4a1f758e75..6397fe1bdf 100755 --- a/src/main/resources/resource/Solr/core1/conf/managed-schema +++ b/src/main/resources/resource/Solr/core1/conf/managed-schema @@ -2,6 +2,116 @@ id + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -418,88 +528,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html index 0b8fc75058..9f717b8c22 100644 --- a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html @@ -1,9 +1,11 @@
    +
    - + +

    {{filter}} [x] From 1d7c49825535f45c6d26eca35d7561d843ecc5bc Mon Sep 17 00:00:00 2001 From: kwatters Date: Mon, 26 Jun 2023 17:19:55 -0400 Subject: [PATCH 14/54] adding some batching support in solr, some more schema updates --- .../java/org/myrobotlab/service/Solr.java | 85 ++++++++++--------- .../resource/Solr/core1/conf/managed-schema | 35 +++++--- .../resource/WebGui/app/service/js/SolrGui.js | 2 +- 3 files changed, 69 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 655bbb048f..d2cc36d0bf 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -7,6 +7,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -79,6 +80,10 @@ public class Solr extends Service implements DocumentListener, TextListener, Mes public String solrHome = "Solr"; // EmbeddedSolrServer embeddedSolrServer = null; transient private EmbeddedSolrServer embeddedSolrServer = null; + + Collection documentBatch = Collections.synchronizedCollection(new ArrayList<>()); + // The batch size of documents to accumulate before flushing the batch to solr. + public transient int batchSize = 100; // TODO: consider moving this tagging logic into opencv.. // for now, we'll just set a counter that will count down how many opencv // frames @@ -163,43 +168,51 @@ public void startEmbedded(String path) throws SolrServerException, IOException { * Add a single document at a time to the solr server. * * @param doc - * the input doc to send to solr - * + * the input doc to send to solr (prefer to send batches with addDocuments instead) + * */ public void addDocument(SolrInputDocument doc) { - try { - if (embeddedSolrServer != null) { - embeddedSolrServer.add(doc); - } else { - solrServer.add(doc); - } - } catch (SolrServerException e) { - // TODO : retry? - log.warn("An exception occurred when trying to add document to the index.", e); - } catch (IOException e) { - // TODO : maybe retry? - log.warn("A network exception occurred when trying to add document to the index.", e); - } + // Always batch! + ArrayList docs = new ArrayList(); + docs.add(doc); + addDocuments(docs); } /** - * Add a batch of documents (this is more effecient than adding one at a time. + * Add a batch of documents (this is more efficient than adding one at a time.) * * @param docs * a collection of solr input docs to add to solr. */ public void addDocuments(Collection docs) { - try { - if (embeddedSolrServer != null) { - embeddedSolrServer.add(docs); - } else { - solrServer.add(docs); + // TODO: setup a thread to flush this batch + // performance optimization.. always batch updates to solr. + documentBatch.addAll(docs); + flushDocumentBatch(false); + } + + private ProcessingStatus flushDocumentBatch(boolean forceFlush) { + synchronized (documentBatch) { + if (documentBatch.size() >= batchSize || forceFlush) { + try { + if (embeddedSolrServer != null) { + embeddedSolrServer.add(documentBatch); + } else { + solrServer.add(documentBatch); + } + // we sent the batch, so let's clear it up. + documentBatch.clear(); + } catch (SolrServerException e) { + log.warn("An exception occurred when trying to add documents to the index.", e); + // ?? + return ProcessingStatus.ERROR; + } catch (IOException e) { + log.warn("A network exception occurred when trying to add documents to the index.", e); + return ProcessingStatus.ERROR; + } } - } catch (SolrServerException e) { - log.warn("An exception occurred when trying to add documents to the index.", e); - } catch (IOException e) { - log.warn("A network exception occurred when trying to add documents to the index.", e); } + return ProcessingStatus.OK; } /** @@ -615,6 +628,7 @@ public void startService() { } @Override + // TODO: why do we return ProcessingStatus here?! public ProcessingStatus onDocuments(List docs) { // Convert the input document to a solr input docs and send it! if (docs.size() == 0) { @@ -623,19 +637,9 @@ public ProcessingStatus onDocuments(List docs) { } ArrayList docsToSend = new ArrayList(); for (Document d : docs) { - docsToSend.add(convertDocument(d)); - } - try { - if (embeddedSolrServer != null) { - embeddedSolrServer.add(docsToSend); - } else { - solrServer.add(docsToSend); - } - return ProcessingStatus.OK; - } catch (Exception e) { - log.warn("Exception in Solr onDocuments.", e); - return ProcessingStatus.DROP; + documentBatch.add(convertDocument(d)); } + return flushDocumentBatch(false); } private SolrInputDocument convertDocument(Document doc) { @@ -667,11 +671,8 @@ public ProcessingStatus onDocument(Document doc) { @Override public boolean onFlush() { - // NoOp currently, but at some point if we change how this service batches - // it's - // add messages to solr, we could revisit this. - // or maybe issue a commit here? I hate committing the index so frequently, - // but maybe it's ok. + // if we got a flush call, let's flush any partial batch. + flushDocumentBatch(true); if (commitOnFlush) { commit(); } diff --git a/src/main/resources/resource/Solr/core1/conf/managed-schema b/src/main/resources/resource/Solr/core1/conf/managed-schema index 6397fe1bdf..a7cfd14c36 100755 --- a/src/main/resources/resource/Solr/core1/conf/managed-schema +++ b/src/main/resources/resource/Solr/core1/conf/managed-schema @@ -9,31 +9,46 @@ - - - - - + + + + + + + + + + + + + + + - + + + + + + - - - - + + + + +
    + + {{result.title[0]}}
    + Artist: {{result.artist[0]}}
    + Album: {{result.album[0]}} Year: {{result.year[0]}}
    + Genre: {{result.genre[0]}}
    + Filepath: {{result.filepath[0]}} +
    + +
    + Doc: {{$index + startOffset + 1}} + @@ -49,7 +61,8 @@
    {{key}}{{value}}
    - + +
    From 7899ba0863fc7ebb55d365986f37acee4dd15ff3 Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 27 Jul 2023 15:01:34 -0400 Subject: [PATCH 23/54] making solr flush it's partial batch if a commit is invoked. --- src/main/java/org/myrobotlab/service/Solr.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 86ef140a0c..043287d923 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -134,13 +134,17 @@ public void deleteEmbeddedIndex() throws SolrServerException, IOException { * @throws IOException * boom */ - public void startEmbedded(String path) throws SolrServerException, IOException { + public synchronized void startEmbedded(String path) throws SolrServerException, IOException { // let's extract our default configs into the directory/ // FileIO.extract(Util.getResourceDir() , "Solr/core1", path); // FileIO.extract(Util.getResourceDir() , "Solr/solr.xml", path + // File.separator + "solr.xml"); // load up the solr core container and start solr - + if (embeddedSolrServer != null) { + log.info("Embedded solr already running."); + return; + } + // FIXME - a bit unsatisfactory File f = new File(getDataInstanceDir()); f.mkdirs(); @@ -222,6 +226,9 @@ private ProcessingStatus flushDocumentBatch(boolean forceFlush) { * */ public void commit() { + // if we are explicitly calling a commit.. first flush any partial batch + // followed by the commit. + flushDocumentBatch(true); try { if (embeddedSolrServer != null) { embeddedSolrServer.commit(); From b784c4ab3cc3380dd18947e2fa576ea4bfdb9194 Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 27 Jul 2023 17:33:17 -0400 Subject: [PATCH 24/54] adding a default mediasearch config that will crawl the folder z:\Music --- .../resource/config/mediasearch/audiofile.yml | 15 ++++++++ .../resource/config/mediasearch/docproc.yml | 36 +++++++++++++++++++ .../config/mediasearch/fileconnector.yml | 11 ++++++ .../resource/config/mediasearch/python.yml | 11 ++++++ .../resource/config/mediasearch/runtime.yml | 20 +++++++++++ .../resource/config/mediasearch/security.yml | 4 +++ .../resource/config/mediasearch/solr.yml | 6 ++++ .../resource/config/mediasearch/webgui.yml | 10 ++++++ 8 files changed, 113 insertions(+) create mode 100755 src/main/resources/resource/config/mediasearch/audiofile.yml create mode 100755 src/main/resources/resource/config/mediasearch/docproc.yml create mode 100755 src/main/resources/resource/config/mediasearch/fileconnector.yml create mode 100755 src/main/resources/resource/config/mediasearch/python.yml create mode 100755 src/main/resources/resource/config/mediasearch/runtime.yml create mode 100755 src/main/resources/resource/config/mediasearch/security.yml create mode 100755 src/main/resources/resource/config/mediasearch/solr.yml create mode 100755 src/main/resources/resource/config/mediasearch/webgui.yml diff --git a/src/main/resources/resource/config/mediasearch/audiofile.yml b/src/main/resources/resource/config/mediasearch/audiofile.yml new file mode 100755 index 0000000000..f7864e5f28 --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/audiofile.yml @@ -0,0 +1,15 @@ +!!org.myrobotlab.service.config.AudioFileConfig +audioListeners: [ + ] +currentPlaylist: default +currentTrack: default +listeners: null +mute: false +peakDelayMs: null +peakMultiplier: 100.0 +peakSampleInterval: 15.0 +peers: null +playlists: { + } +type: AudioFile +volume: 1.0 diff --git a/src/main/resources/resource/config/mediasearch/docproc.yml b/src/main/resources/resource/config/mediasearch/docproc.yml new file mode 100755 index 0000000000..159c6fd091 --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/docproc.yml @@ -0,0 +1,36 @@ +!!org.myrobotlab.service.config.DocumentPipelineConfig +listeners: +- callback: onFlush + listener: solr + method: publishFlush +- callback: onDocument + listener: solr + method: publishDocument +peers: null +type: DocumentPipeline +workFlowConfig: + config: { + } + name: default + numWorkerThreads: 8 + queueLength: 50 + stages: + - config: + type: file + stageClass: org.myrobotlab.document.transformer.SetStaticFieldValue + stageName: SetTypeField + - config: { + } + stageClass: org.myrobotlab.document.transformer.TextExtractor + stageName: TextExtractor + - config: + fieldNameMap: + xmpdm_duration: duration + xmpdm_genre: genre + dc_title: title + xmpdm_tracknumber: tracknumber + xmpdm_artist: artist + xmpdm_album: album + xmpdm_releasedate: year + stageClass: org.myrobotlab.document.transformer.RenameFields + stageName: RenameFields diff --git a/src/main/resources/resource/config/mediasearch/fileconnector.yml b/src/main/resources/resource/config/mediasearch/fileconnector.yml new file mode 100755 index 0000000000..f505ff3e98 --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/fileconnector.yml @@ -0,0 +1,11 @@ +!!org.myrobotlab.service.config.FileConnectorConfig +directory: Z:\Music +listeners: +- callback: onFlush + listener: docproc + method: publishFlush +- callback: onDocument + listener: docproc + method: publishDocument +peers: null +type: FileConnector diff --git a/src/main/resources/resource/config/mediasearch/python.yml b/src/main/resources/resource/config/mediasearch/python.yml new file mode 100755 index 0000000000..c372d6ec5d --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/python.yml @@ -0,0 +1,11 @@ +!!org.myrobotlab.service.config.PythonConfig +listeners: null +modulePaths: +- resource\Python\modules +peers: null +scriptRootDir: C:\dev\workspace.mrl4\myrobotlab\data\Python\python +startScripts: [ + ] +stopScripts: [ + ] +type: Python diff --git a/src/main/resources/resource/config/mediasearch/runtime.yml b/src/main/resources/resource/config/mediasearch/runtime.yml new file mode 100755 index 0000000000..9e2fb0c0a5 --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/runtime.yml @@ -0,0 +1,20 @@ +!!org.myrobotlab.service.config.RuntimeConfig +enableCli: true +id: null +listeners: [ + ] +locale: null +logLevel: info +peers: null +registry: +- runtime +- security +- python +- webgui +- solr +- audiofile +- docproc +- fileconnector +resource: resource +type: Runtime +virtual: false diff --git a/src/main/resources/resource/config/mediasearch/security.yml b/src/main/resources/resource/config/mediasearch/security.yml new file mode 100755 index 0000000000..aa33d68612 --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/security.yml @@ -0,0 +1,4 @@ +!!org.myrobotlab.service.config.ServiceConfig +listeners: null +peers: null +type: Security diff --git a/src/main/resources/resource/config/mediasearch/solr.yml b/src/main/resources/resource/config/mediasearch/solr.yml new file mode 100755 index 0000000000..34aba0b58b --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/solr.yml @@ -0,0 +1,6 @@ +!!org.myrobotlab.service.config.SolrConfig +embedded: true +listeners: null +peers: null +solrUrl: http://localhost:8983/solr +type: Solr diff --git a/src/main/resources/resource/config/mediasearch/webgui.yml b/src/main/resources/resource/config/mediasearch/webgui.yml new file mode 100755 index 0000000000..4744de99ba --- /dev/null +++ b/src/main/resources/resource/config/mediasearch/webgui.yml @@ -0,0 +1,10 @@ +!!org.myrobotlab.service.config.WebGuiConfig +autoStartBrowser: true +enableMdns: false +listeners: null +peers: null +port: 8888 +resources: +- ./resource/WebGui/app +- ./resource +type: WebGui From f92549a5ad2d8c52729760579700099a6e7b725a Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 27 Jul 2023 18:18:24 -0400 Subject: [PATCH 25/54] some of APs feedback --- .../document/transformer/Configuration.java | 9 ++------- .../document/transformer/StageConfiguration.java | 4 ---- src/main/java/org/myrobotlab/service/Solr.java | 8 ++------ .../resources/resource/config/mediasearch/python.yml | 11 ----------- .../resources/resource/config/mediasearch/runtime.yml | 1 - 5 files changed, 4 insertions(+), 29 deletions(-) delete mode 100755 src/main/resources/resource/config/mediasearch/python.yml diff --git a/src/main/java/org/myrobotlab/document/transformer/Configuration.java b/src/main/java/org/myrobotlab/document/transformer/Configuration.java index 2479c433ee..366266093e 100644 --- a/src/main/java/org/myrobotlab/document/transformer/Configuration.java +++ b/src/main/java/org/myrobotlab/document/transformer/Configuration.java @@ -8,7 +8,7 @@ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.StaxDriver; -public class Configuration implements Serializable { +public abstract class Configuration implements Serializable { // TODO: add a map type. // TODO: push the name/class onto this ? @@ -22,14 +22,9 @@ public class Configuration implements Serializable { * */ private static final long serialVersionUID = 1L; - public HashMap config = null; - // private XStream xstream = null; + public HashMap config = new HashMap(); public Configuration() { - config = new HashMap(); - // figure that we need to be able to serialize / deserialize - // TODO: consider a faster driver / serializer - // xstream = new XStream(new StaxDriver()); } public void setStringParam(String name, String value) { diff --git a/src/main/java/org/myrobotlab/document/transformer/StageConfiguration.java b/src/main/java/org/myrobotlab/document/transformer/StageConfiguration.java index 0359931814..6243f41c88 100644 --- a/src/main/java/org/myrobotlab/document/transformer/StageConfiguration.java +++ b/src/main/java/org/myrobotlab/document/transformer/StageConfiguration.java @@ -4,20 +4,16 @@ public class StageConfiguration extends Configuration { - // private HashMap config = null; - private String stageName = "defaultStage"; private String stageClass = "org.myrobotlab.document.transformer.AbstractStage"; public StageConfiguration(String stageName, String stageClass) { - config = new HashMap(); this.stageName = stageName; this.stageClass = stageClass; } public StageConfiguration() { // depricate this constructor? - config = new HashMap(); } @Override diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 043287d923..5445f607f0 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -177,9 +177,7 @@ public synchronized void startEmbedded(String path) throws SolrServerException, */ public void addDocument(SolrInputDocument doc) { // Always batch! - ArrayList docs = new ArrayList(); - docs.add(doc); - addDocuments(docs); + addDocuments(List.of(doc)); } /** @@ -670,11 +668,9 @@ private SolrInputDocument convertDocument(Document doc) { @Override public ProcessingStatus onDocument(Document doc) { // always be batching when sending docs. - ArrayList docs = new ArrayList(); - docs.add(doc); // TODO: we want to add to the current batch to send.. // and make sure we have a thread flushing the batch if it gets too old. - return onDocuments(docs); + return onDocuments(List.of(doc)); } @Override diff --git a/src/main/resources/resource/config/mediasearch/python.yml b/src/main/resources/resource/config/mediasearch/python.yml deleted file mode 100755 index c372d6ec5d..0000000000 --- a/src/main/resources/resource/config/mediasearch/python.yml +++ /dev/null @@ -1,11 +0,0 @@ -!!org.myrobotlab.service.config.PythonConfig -listeners: null -modulePaths: -- resource\Python\modules -peers: null -scriptRootDir: C:\dev\workspace.mrl4\myrobotlab\data\Python\python -startScripts: [ - ] -stopScripts: [ - ] -type: Python diff --git a/src/main/resources/resource/config/mediasearch/runtime.yml b/src/main/resources/resource/config/mediasearch/runtime.yml index 9e2fb0c0a5..cf77865501 100755 --- a/src/main/resources/resource/config/mediasearch/runtime.yml +++ b/src/main/resources/resource/config/mediasearch/runtime.yml @@ -9,7 +9,6 @@ peers: null registry: - runtime - security -- python - webgui - solr - audiofile From cce9205c56a4e36bc271f4e2d2ca74d06d1f4cee Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 27 Jul 2023 19:22:51 -0400 Subject: [PATCH 26/54] make the document pipeline initalize in the apply method. --- .../java/org/myrobotlab/service/DocumentPipeline.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index e578239625..2f1dc0de22 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -152,7 +152,7 @@ public static void main(String[] args) throws Exception { // connector to pipeline connection connector.attachDocumentListener(pipeline.getName()); - Runtime.saveConfig("musicsearch"); + Runtime.saveConfig("mediasearch"); // start the crawl! boolean doCrawl = false; if (doCrawl) { @@ -216,6 +216,12 @@ public ServiceConfig apply(ServiceConfig inConfig) { DocumentPipelineConfig config = (DocumentPipelineConfig)super.apply(inConfig); // this.workFlowConfig = config.workFlowConfig; + try { + initalize(); + } catch (ClassNotFoundException e) { + log.error("Error initializing the document pipeline.", e); + // TODO: shoiuld we throw some runtime here? + } return config; } From da06ebe51b9fc166491db809afba67518dac8611 Mon Sep 17 00:00:00 2001 From: kwatters Date: Thu, 27 Jul 2023 19:45:23 -0400 Subject: [PATCH 27/54] minor ui clean up and increase the number of results displayed. --- src/main/java/org/myrobotlab/service/Solr.java | 2 +- src/main/resources/resource/WebGui/app/service/js/SolrGui.js | 2 +- .../resources/resource/WebGui/app/service/views/SolrGui.html | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 5445f607f0..3ccaf4dc53 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -532,7 +532,7 @@ public QueryResponse search(String queryString, int rows, int start, boolean mos */ public QueryResponse searchWithFacets(String queryString, int rows, int start, String[] facetFields, String[] filters) { log.info("Searching for (with facets): {}", queryString); - int numFacetBuckets = 20; + int numFacetBuckets = 50; SolrQuery query = new SolrQuery(); query.set("q", queryString); query.setRows(rows); diff --git a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js index 470740667f..33366c77a7 100644 --- a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js @@ -8,7 +8,7 @@ angular.module('mrlapp.service.SolrGui', []).controller('SolrGuiCtrl', ['$scope' $scope.startOffset = 0; $scope.endOffset = 0; $scope.numFound = 0; - $scope.pageSize = 20; + $scope.pageSize = 50; $scope.filters = []; // TODO: maybe some other fields.. // TODO: support range facets diff --git a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html index 1ea5abab6f..1f380f05b8 100644 --- a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html @@ -42,7 +42,8 @@
    - {{result.title[0]}}
    + + Artist: {{result.artist[0]}}
    Album: {{result.album[0]}} Year: {{result.year[0]}}
    Genre: {{result.genre[0]}}
    From 2ee3e40ae40b268f9180559609a43aa51aa90a50 Mon Sep 17 00:00:00 2001 From: kwatters Date: Fri, 28 Jul 2023 01:48:57 -0400 Subject: [PATCH 28/54] don't use a local config, small ui updates --- src/main/java/org/myrobotlab/service/Solr.java | 4 ++-- .../resources/resource/WebGui/app/service/js/SolrGui.js | 2 +- .../resource/WebGui/app/service/views/SolrGui.html | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index 3ccaf4dc53..cd455375b5 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -69,7 +69,7 @@ */ public class Solr extends Service implements DocumentListener, TextListener, MessageListener { - private SolrConfig config; + private static final String CORE_NAME = "core1"; public final static Logger log = LoggerFactory.getLogger(Solr.class); private static final long serialVersionUID = 1L; @@ -1166,7 +1166,7 @@ public void releaseService() { @Override public ServiceConfig apply(ServiceConfig inConfig) { // - this.config = (SolrConfig)super.apply(inConfig); + SolrConfig config = (SolrConfig)super.apply(inConfig); if (config.embedded) { // try { diff --git a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js index 33366c77a7..1db0666642 100644 --- a/src/main/resources/resource/WebGui/app/service/js/SolrGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/SolrGui.js @@ -60,7 +60,7 @@ angular.module('mrlapp.service.SolrGui', []).controller('SolrGuiCtrl', ['$scope' // run the search based on the current query params selected. $scope.execSearch = function() { - mrl.sendTo($scope.service.name, "searchWithFacets", $scope.queryString, 10, $scope.startOffset, $scope.facetFields, $scope.filters); + mrl.sendTo($scope.service.name, "searchWithFacets", $scope.queryString, $scope.pageSize, $scope.startOffset, $scope.facetFields, $scope.filters); } $scope.filter = function(field, value) { diff --git a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html index 1f380f05b8..210ddc84e4 100644 --- a/src/main/resources/resource/WebGui/app/service/views/SolrGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/SolrGui.html @@ -44,9 +44,10 @@ - Artist: {{result.artist[0]}}
    - Album: {{result.album[0]}} Year: {{result.year[0]}}
    - Genre: {{result.genre[0]}}
    +
    Artist: {{result.artist[0]}}
    +
    Album: {{result.album[0]}}
    +
    Year: {{result.year[0]}}
    +
    Genre: {{result.genre[0]}}
    Filepath: {{result.filepath[0]}}
    From b91e60197ecda0385765a43843e9b829d4dfa58f Mon Sep 17 00:00:00 2001 From: grog Date: Sat, 29 Jul 2023 06:32:26 -0700 Subject: [PATCH 29/54] opencv vp needs to be transient --- src/main/java/org/myrobotlab/service/OpenCV.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index 78d6214b32..b18858bad4 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -550,7 +550,7 @@ public static IplImage cropImage(IplImage img, CvRect rect) { boolean undockDisplay = false; - final private VideoProcessor vp = new VideoProcessor(); + final transient private VideoProcessor vp = new VideoProcessor(); Integer width = null; From 8bf4f662a67ddceb4489187fccd908ffcbabb837 Mon Sep 17 00:00:00 2001 From: GroG Date: Sat, 29 Jul 2023 20:59:32 -0700 Subject: [PATCH 30/54] pir fix (#1322) --- .../java/org/myrobotlab/codec/CodecUtils.java | 10 + .../org/myrobotlab/framework/Service.java | 55 +++-- src/main/java/org/myrobotlab/service/Pir.java | 189 +++++++++++------- .../myrobotlab/service/config/PirConfig.java | 1 + .../resource/WebGui/app/service/js/PirGui.js | 42 +++- .../WebGui/app/service/views/PirGui.html | 14 +- .../resource/WebGui/app/widget/oscope.js | 2 +- 7 files changed, 215 insertions(+), 98 deletions(-) diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index 9401452e04..d1b50fa6fc 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -1634,5 +1634,15 @@ public static String toBase64(byte[] bytes) { public static byte[] fromBase64(String input) { return Base64.getDecoder().decode(input); } + + public static String removeEnd(final String str, final String remove) { + if (str == null || str.length() == 0 || remove == null || remove.length() == 0) { + return str; + } + if (str.endsWith(remove)) { + return str.substring(0, str.length() - remove.length()); + } + return str; +} } diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index df0d189811..3bee512f48 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -25,6 +25,7 @@ package org.myrobotlab.framework; +// java or mrl imports only - no dependencies ! import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -54,7 +55,6 @@ import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArrayList; -import org.apache.commons.lang3.StringUtils; import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.framework.interfaces.Broadcaster; @@ -1012,22 +1012,47 @@ public String[] getMethodNames() { public Method[] getMethods() { return this.getClass().getMethods(); } - + + /** + * Returns a map containing all interface names from the class hierarchy and the interface hierarchy of the + * current class. + * + * @return A map containing all interface names. + */ public Map getInterfaceSet() { - Map ret = new TreeMap<>(); - Class c = getClass(); - while (c != Object.class) { - - Class[] interfaces = c.getInterfaces(); - for (Class interfaze : interfaces) { - // ya silly :P - but gson's default conversion of a HashSet is an - // array - ret.put(interfaze.getName(), interfaze.getName()); + Map ret = new TreeMap<>(); + Set> visitedClasses = new HashSet<>(); + getAllInterfacesHelper(getClass(), ret, visitedClasses); + return ret; + } + + /** + * Recursively traverses the class hierarchy and the interface hierarchy to add all interface names to the + * specified map. + * + * @param c The class to start the traversal from. + * @param ret The map to store the interface names. + * @param visitedClasses A set to keep track of visited classes to avoid infinite loops. + */ + private void getAllInterfacesHelper(Class c, Map ret, Set> visitedClasses) { + if (c != null && !visitedClasses.contains(c)) { + // Add interfaces from the current class + Class[] interfaces = c.getInterfaces(); + for (Class interfaze : interfaces) { + ret.put(interfaze.getName(), interfaze.getName()); + } + + // Add interfaces from interfaces implemented by the current class + for (Class interfaze : interfaces) { + getAllInterfacesHelper(interfaze, ret, visitedClasses); + } + + // Recursively traverse the superclass hierarchy + visitedClasses.add(c); + getAllInterfacesHelper(c.getSuperclass(), ret, visitedClasses); } - c = c.getSuperclass(); - } - return ret; } + public Message getMsg() throws InterruptedException { return inbox.getMsg(); @@ -1424,7 +1449,7 @@ public ServiceConfig getConfig() { // so doesn't conflict with remote routes Listener newConfigListener = new Listener( listener.topicMethod, - StringUtils.removeEnd( + CodecUtils.removeEnd( listener.callbackName, '@' + Platform.getLocalInstance().getId() ), diff --git a/src/main/java/org/myrobotlab/service/Pir.java b/src/main/java/org/myrobotlab/service/Pir.java index 21269b2dbd..bc5f35328c 100644 --- a/src/main/java/org/myrobotlab/service/Pir.java +++ b/src/main/java/org/myrobotlab/service/Pir.java @@ -1,13 +1,18 @@ package org.myrobotlab.service; +import java.util.ArrayList; +import java.util.List; + +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.Service; -import org.myrobotlab.framework.interfaces.ServiceInterface; +import org.myrobotlab.framework.TimeoutException; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.PirConfig; import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.PinData; import org.myrobotlab.service.interfaces.PinArrayControl; +import org.myrobotlab.service.interfaces.PinDefinition; import org.myrobotlab.service.interfaces.PinListener; import org.slf4j.Logger; @@ -24,103 +29,96 @@ public class Pir extends Service implements PinListener { */ Boolean active = null; - /** - * The pin to be used as a string. Example "D4" or "A0". - */ - // String pin; - - transient PinArrayControl pinControl; - /** * Timestamp of the last poll. */ Long lastChangeTs = null; - boolean attached = false; + protected boolean isAttached = false; public Pir(String n, String id) { super(n, id); - registerForInterfaceChange(PinArrayControl.class); } @Deprecated /* use attach(String) or attachPinArrayControl(PinArrayControl) */ public void attach(PinArrayControl control, String pin) { setPin(pin); - attachPinArrayControl(control); + attachPinArrayControl(control.getName()); } @Override public void attach(String name) { - ServiceInterface si = Runtime.getService(name); - if (si instanceof PinArrayControl) { - attachPinArrayControl((PinArrayControl) si); - } else { - error("do not know how to attach to %s of type %s", name, si.getSimpleName()); - } + attachPinArrayControl(name); } - public void attachPinArrayControl(PinArrayControl control) { + public void setPinArrayControl(String control) { PirConfig c = (PirConfig) config; - try { - this.pinControl = control; - c.controller = control.getName(); - - if (c.pin == null) { - error("pin should be set before attaching"); - } - pinControl.attach(getName()); - attached = true; - broadcastState(); - } catch (Exception e) { - error(e); - } + c.controller = control; } - @Override - public void detach(String name) { + public void attachPinArrayControl(String control) { PirConfig c = (PirConfig) config; + + if (control == null) { + error("controller cannot be null"); + return; + } + + if (c.pin == null) { + error("pin should be set before attaching"); + return; + } + + c.controller = CodecUtils.getShortName(control); + + // fire and forget + send(c.controller, "attach", getName()); + // assume worky + isAttached = true; - ServiceInterface si = Runtime.getService(name); - if (si instanceof PinArrayControl && c.pin != null) { - // FIXME - problem - what if someone else is using this pin ? - // FIXME - should disable in the context of this service's name - ((PinArrayControl) si).disablePin(c.pin); - detachPinArrayControl((PinArrayControl) si); - } + // enable if configured + if (c.enable) { + send(c.controller, "enablePin", c.pin, c.rate); + } - active = null; - c.enable = false; - attached = false; broadcastState(); } - public void detachPinArrayControl(PinArrayControl control) { + @Override + public void detach(String name) { + detachPinArrayControl(name); + } + + /** + * FIXME - use interface of service names not direct references + * + * @param control + */ + public void detachPinArrayControl(String control) { PirConfig c = (PirConfig) config; - try { if (control == null) { log.info("detaching null"); return; } if (c.controller != null) { - if (!c.controller.equals(control.getName())) { - log.warn("attempting to detach {} but this pir is attached to {}", control.getName(), c.controller); + if (!c.controller.equals(control)) { + log.warn("attempting to detach {} but this pir is attached to {}", control, c.controller); return; } } - // FYI - we could detach like this without a reference - good for remote - // send(controllerName, "detach", getName()); - pinControl.detach(getName()); + // disable + disable(); + + send(c.controller, "detach", getName()); + // c.controller = null; left as configuration .. "last controller" - this.pinControl = null; - c.controller = null; + // detached + isAttached = false; broadcastState(); - } catch (Exception e) { - error(e); - } } /** @@ -132,8 +130,8 @@ public void detachPinArrayControl(PinArrayControl control) { public void disable() { PirConfig c = (PirConfig) config; - if (pinControl != null && c.pin != null) { - pinControl.disablePin(c.pin); + if (c.controller != null && c.pin != null) { + send(c.controller, "disablePin", c.pin); } c.enable = false; @@ -158,7 +156,7 @@ public void enable() { public void enable(int rateHz) { PirConfig c = (PirConfig) config; - if (pinControl == null) { + if (c.controller == null) { error("pin control not set"); return; } @@ -168,13 +166,14 @@ public void enable(int rateHz) { return; } - if (rateHz < 1) { + if (rateHz < 0) { error("invalid poll rate - default is 1 Hz valid value is > 0"); return; } c.rate = rateHz; - pinControl.enablePin(c.pin, rateHz); + /* PinArrayControl.enablePin */ + send(c.controller, "enablePin", c.pin, rateHz); c.enable = true; broadcastState(); } @@ -219,8 +218,12 @@ public boolean isEnabled() { @Override public ServiceConfig apply(ServiceConfig c) { PirConfig config = (PirConfig) super.apply(c); + + if (config.controller != null) { + attach(config.controller);; + } - if (config.enable) { + if (config.enable) { enable(config.rate); } else { disable(); @@ -228,16 +231,29 @@ public ServiceConfig apply(ServiceConfig c) { return c; } + + @Override + public ServiceConfig getConfig() { + PirConfig c = (PirConfig)super.getConfig(); + if (c.controller != null) { + // it makes sense that the controller should always be local for a PIR + // but in general this is bad practice on 2 levels + // 1. in some other context it might make sense not to be local + // 2. it should just be another listener on ServiceConfig.listener + c.controller=CodecUtils.getShortName(c.controller); + } + return c; + } @Override public void onPin(PinData pindata) { - + PirConfig c = (PirConfig) config; log.debug("onPin {}", pindata); boolean sense = (pindata.value != 0); - // sparse publishing only on state change - if (active == null || active != sense) { + // sparse publishing only on state change + if (active == null || active != sense && c.enable) { // state change invoke("publishSense", sense); active = sense; @@ -255,11 +271,11 @@ public Boolean publishSense(Boolean b) { } public void publishPirOn() { - log.info("publishPirOn"); + log.debug("publishPirOn"); } public void publishPirOff() { - log.info("publishPirOff"); + log.debug("publishPirOff"); } /** @@ -277,7 +293,6 @@ public void setPin(String pin) { @Deprecated /* use attach(String) */ public void setPinArrayControl(PinArrayControl pinControl) { PirConfig c = (PirConfig) config; - this.pinControl = pinControl; c.controller = pinControl.getName(); } @@ -304,23 +319,53 @@ public Long getLastChangeTs() { return lastChangeTs; } + /** + * This returns the pin list of the selected PinArrayControl. This allows + * dynamic selection of a pin based on a query to a PinArrayControl. It would + * be advisable that other services manage pins in the same way. Where + * "selecting" the controller's name, returns the possible list of pins to + * attach. + * + * @return + * @throws InterruptedException + * @throws TimeoutException + */ + @SuppressWarnings("unchecked") + public List getPinList(String pinArrayControl) { + List pinList = new ArrayList<>(); + try { + if (pinArrayControl != null) { + pinList = (List) sendBlocking(pinArrayControl, "getPinList"); + } + } catch (Exception e) { + error(e); + } + return pinList; + } + public static void main(String[] args) { try { LoggingFactory.init("info"); - Pir pir = (Pir) Runtime.start("pir", "Pir"); - pir.setPin("D6"); - Runtime.start("webgui", "WebGui"); - Arduino mega = (Arduino) Runtime.start("mega", "Arduino"); - mega.connect("/dev/ttyACM2"); + // Runtime.start("webgui", "WebGui"); + + // standard install - develop and debug using config + Runtime.main(new String[] {"--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python"}); + boolean done = true; if (done) { return; } + Pir pir = (Pir) Runtime.start("pir", "Pir"); + pir.setPin("D23"); + + Arduino mega = (Arduino) Runtime.start("mega", "Arduino"); + mega.connect("/dev/ttyACM71"); + mega.attach(pir); // Runtime.setAllVirtual(true); diff --git a/src/main/java/org/myrobotlab/service/config/PirConfig.java b/src/main/java/org/myrobotlab/service/config/PirConfig.java index c1413bb9f1..3d67430959 100644 --- a/src/main/java/org/myrobotlab/service/config/PirConfig.java +++ b/src/main/java/org/myrobotlab/service/config/PirConfig.java @@ -14,6 +14,7 @@ public class PirConfig extends ServiceConfig { /** * poll rate in Hz + * FIXME change to double or float to support 0.01 Hz */ public int rate = 1; diff --git a/src/main/resources/resource/WebGui/app/service/js/PirGui.js b/src/main/resources/resource/WebGui/app/service/js/PirGui.js index cef3c00463..a19bb57b78 100644 --- a/src/main/resources/resource/WebGui/app/service/js/PirGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/PirGui.js @@ -12,8 +12,14 @@ angular.module('mrlapp.service.PirGui', []).controller('PirGuiCtrl', ['$scope', // GOOD TEMPLATE TO FOLLOW this.updateState = function(service) { $scope.service = service - $scope.options.attachName = service.config.controller + $scope.options.attachName = service.config.controller $scope.options.isAttached = service.attached + $scope.options.interface = 'PinArrayControl' + + // since attach broadcasts we'll get the pin list here + if ($scope?.service?.config?.controller) { + msg.send('getPinList', $scope.service.config.controller) + } } // init scope variables @@ -27,6 +33,13 @@ angular.module('mrlapp.service.PirGui', []).controller('PirGuiCtrl', ['$scope', _self.updateState(data) $scope.$apply() break + case 'onPinList': + $scope.pinList = [] + for (var pinDef of data) { + $scope.pinList.push(pinDef.pin) + } + $scope.$apply() + break case 'onSense': console.info('onSense', data) $scope.service.active = data @@ -41,6 +54,9 @@ angular.module('mrlapp.service.PirGui', []).controller('PirGuiCtrl', ['$scope', _self.selectController = function(controller) { //$scope.service.controllerName = controller $scope.service.config.controller = controller + // get the pin list of the selected controller + msg.send('setPinArrayControl', controller) + msg.send('getPinList', controller) } $scope.options = { @@ -53,6 +69,7 @@ angular.module('mrlapp.service.PirGui', []).controller('PirGuiCtrl', ['$scope', $scope.attach = function() { msg.send('setPin', $scope.service.config.pin) msg.send('attach', $scope.service.config.controller) + msg.send('enable') } $scope.detach = function() { @@ -67,8 +84,10 @@ angular.module('mrlapp.service.PirGui', []).controller('PirGuiCtrl', ['$scope', } $scope.setPin = function() { - msg.send('setPin', $scope.service.config.pin) - msg.send('broadcastState') + if ($scope.service.config.pin) { + msg.send('setPin', $scope.service.config.pin) + msg.send('broadcastState') + } } $scope.disable = function() { @@ -76,7 +95,24 @@ angular.module('mrlapp.service.PirGui', []).controller('PirGuiCtrl', ['$scope', msg.send('broadcastState') } + $scope.getActiveImage = function() { + if ($scope.service.active) { + return '../../green.png' + } else if ($scope.service.active === false) { + return '../../red.png' + } else { + // undefined / unknown + return '../../grey.png' + } + } + msg.subscribe('publishSense') + msg.subscribe('getPinList') + + if ($scope?.service?.config?.controller) { + msg.send('getPinList', $scope.service.config.controller) + } + msg.subscribe(this) } ]) diff --git a/src/main/resources/resource/WebGui/app/service/views/PirGui.html b/src/main/resources/resource/WebGui/app/service/views/PirGui.html index 5a1f8fe4e2..e359428b82 100644 --- a/src/main/resources/resource/WebGui/app/service/views/PirGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/PirGui.html @@ -1,21 +1,21 @@

    - poll rate {{service.config.rate}} hz + poll rate {{service.config.rate}} hz

    pin - - - - - + + + +
    -
    +
    \ No newline at end of file diff --git a/src/main/resources/resource/WebGui/app/widget/oscope.js b/src/main/resources/resource/WebGui/app/widget/oscope.js index f779cafa2d..11cc94c2c0 100644 --- a/src/main/resources/resource/WebGui/app/widget/oscope.js +++ b/src/main/resources/resource/WebGui/app/widget/oscope.js @@ -165,7 +165,7 @@ angular.module('mrlapp.service').directive('oscope', ['mrl', function(mrl) { // move to last position... ctx.moveTo(trace.x0, trace.y0) trace.x1++ - trace.y1 = c + trace.y1 = y // draw line to x1,y1 ctx.lineTo(trace.x1, trace.y1) // save current values From 915041714f3491e29de4afa2e206eaf349a403c2 Mon Sep 17 00:00:00 2001 From: kwatters Date: Tue, 1 Aug 2023 16:36:00 -0400 Subject: [PATCH 31/54] regenerating the pom and excluding logging dependencies from tika imports. --- pom.xml | 72 ++++++++++++++----- .../service/meta/DocumentPipelineMeta.java | 12 ++++ .../org/myrobotlab/service/meta/Py4jMeta.java | 4 +- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index 51a199ce89..88df131fb3 100644 --- a/pom.xml +++ b/pom.xml @@ -163,10 +163,6 @@ - - - - org.boofcv @@ -305,18 +301,62 @@ - - - org.apache.tika - tika-core - 2.8.0 - - - org.apache.tika - tika-parser-audiovideo-module - 2.8.0 - - + + org.apache.tika + tika-core + 2.8.0 + provided + + + org.slf4j + * + + + log4j + * + + + org.apache.logging.log4j + * + + + com.fasterxml.jackson.core + * + + + io.netty + * + + + + + org.apache.tika + tika-parser-audiovideo-module + 2.8.0 + provided + + + org.slf4j + * + + + log4j + * + + + org.apache.logging.log4j + * + + + com.fasterxml.jackson.core + * + + + io.netty + * + + + org.apache.opennlp opennlp-tools diff --git a/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java b/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java index 3ec1139e97..3a179e93be 100644 --- a/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/DocumentPipelineMeta.java @@ -17,7 +17,19 @@ public DocumentPipelineMeta() { addDescription("This service will pass a document through a document processing pipeline made up of transformers"); addCategory("ingest"); addDependency("org.apache.tika", "tika-core", "2.8.0"); + exclude("org.slf4j", "*"); + exclude("log4j", "*"); + exclude("org.apache.logging.log4j", "*"); + exclude("com.fasterxml.jackson.core", "*"); + exclude("io.netty", "*"); + addDependency("org.apache.tika", "tika-parser-audiovideo-module", "2.8.0"); + exclude("org.slf4j", "*"); + exclude("log4j", "*"); + exclude("org.apache.logging.log4j", "*"); + exclude("com.fasterxml.jackson.core", "*"); + exclude("io.netty", "*"); + addDependency("org.apache.opennlp", "opennlp-tools", "1.6.0"); addDependency("net.objecthunter", "exp4j", "0.4.8"); diff --git a/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java b/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java index def9039a53..f0a36ea333 100644 --- a/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java @@ -22,8 +22,8 @@ public Py4jMeta() { // Used just as a Python exe redistributable. // ABSOLUTELY NO JNI/JNA IS USED - addDependency("org.bytedeco", "cpython-platform", "3.10.8-1.5.8"); - addDependency("org.bytedeco", "cpython", "3.10.8-1.5.8"); + addDependency("org.bytedeco", "cpython-platform", "3.11.3-1.5.9"); + addDependency("org.bytedeco", "cpython", "3.11.3-1.5.9"); } } From 41688a3f820b5668a1968894efb4213091a6f7d8 Mon Sep 17 00:00:00 2001 From: kwatters Date: Tue, 1 Aug 2023 17:08:17 -0400 Subject: [PATCH 32/54] reverting py4j update --- pom.xml | 4 ++-- src/main/java/org/myrobotlab/service/meta/Py4jMeta.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 88df131fb3..02b55ea8ac 100644 --- a/pom.xml +++ b/pom.xml @@ -1283,13 +1283,13 @@ org.bytedeco cpython-platform - 3.11.3-1.5.9 + 3.10.8-1.5.8 provided org.bytedeco cpython - 3.11.3-1.5.9 + 3.10.8-1.5.8 provided diff --git a/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java b/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java index f0a36ea333..def9039a53 100644 --- a/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/Py4jMeta.java @@ -22,8 +22,8 @@ public Py4jMeta() { // Used just as a Python exe redistributable. // ABSOLUTELY NO JNI/JNA IS USED - addDependency("org.bytedeco", "cpython-platform", "3.11.3-1.5.9"); - addDependency("org.bytedeco", "cpython", "3.11.3-1.5.9"); + addDependency("org.bytedeco", "cpython-platform", "3.10.8-1.5.8"); + addDependency("org.bytedeco", "cpython", "3.10.8-1.5.8"); } } From 3f0800a29b07ead19525fe1b60865d4be15a4ced Mon Sep 17 00:00:00 2001 From: GroG Date: Thu, 3 Aug 2023 18:58:57 -0700 Subject: [PATCH 33/54] Fixed path to msg in service api (#1324) * fixed path to msg * update * terse parsing cherry picked * fixed unit tests * no quote params is not an option * regenned pom --- pom.xml | 5 - .../java/org/myrobotlab/codec/ApiSwagger.java | 283 +- .../java/org/myrobotlab/codec/CodecUtils.java | 2660 ++++++++--------- .../org/myrobotlab/codec/MethodCache.java | 79 - .../codec/PolymorphicSerializer.java | 65 - .../GsonPolymorphicTypeAdapterFactory.java | 112 - .../org/myrobotlab/framework/MethodCache.java | 19 +- .../org/myrobotlab/framework/Service.java | 2 +- .../java/org/myrobotlab/framework/Status.java | 2 +- .../myrobotlab/framework/TypeConverter.java | 2 - src/main/java/org/myrobotlab/i2c/I2CBus.java | 2 +- .../org/myrobotlab/process/InProcessCli.java | 19 +- .../java/org/myrobotlab/service/Clock.java | 13 +- .../org/myrobotlab/service/GoogleSearch.java | 1 - .../java/org/myrobotlab/service/Mqtt.java | 2 +- .../org/myrobotlab/service/MqttBroker.java | 25 +- .../java/org/myrobotlab/service/WebGui.java | 55 +- .../java/org/myrobotlab/service/Xmpp.java | 2 +- .../myrobotlab/service/meta/RuntimeMeta.java | 5 +- .../org/myrobotlab/codec/CodecUtilsTest.java | 82 + .../codec/json/GsonPolymorphicTest.java | 128 - .../service/RuntimeProcessTest.java | 26 +- .../org/myrobotlab/service/WebGuiTest.java | 20 +- .../myrobotlab/service/data/LocaleTest.java | 15 +- .../interfaces/SpeechSynthesisTest.java | 15 +- 25 files changed, 1614 insertions(+), 2025 deletions(-) delete mode 100644 src/main/java/org/myrobotlab/codec/MethodCache.java delete mode 100644 src/main/java/org/myrobotlab/codec/PolymorphicSerializer.java delete mode 100644 src/main/java/org/myrobotlab/codec/json/GsonPolymorphicTypeAdapterFactory.java delete mode 100644 src/test/java/org/myrobotlab/codec/json/GsonPolymorphicTest.java diff --git a/pom.xml b/pom.xml index 02b55ea8ac..3dfa83b437 100644 --- a/pom.xml +++ b/pom.xml @@ -1342,11 +1342,6 @@ logback-classic 1.2.3 - - com.google.code.gson - gson - 2.8.5 - net.bytebuddy byte-buddy diff --git a/src/main/java/org/myrobotlab/codec/ApiSwagger.java b/src/main/java/org/myrobotlab/codec/ApiSwagger.java index 1dbf0d4f24..628a0a483e 100644 --- a/src/main/java/org/myrobotlab/codec/ApiSwagger.java +++ b/src/main/java/org/myrobotlab/codec/ApiSwagger.java @@ -1,219 +1,138 @@ package org.myrobotlab.codec; -import java.io.IOException; -import java.io.OutputStream; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; -import java.util.Arrays; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; +import java.util.TreeMap; -import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.interfaces.MessageSender; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.Servo; import org.slf4j.Logger; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; public class ApiSwagger { public final static Logger log = LoggerFactory.getLogger(Runtime.class); - - public Object process(MessageSender sender, OutputStream out, Message msgFromUri, String data) { - - return null; + + static final Map, String> primitiveMap = new HashMap<>(); + static { + primitiveMap.put(int.class, "integer"); + primitiveMap.put(long.class, "integer"); + primitiveMap.put(float.class, "number"); + primitiveMap.put(double.class, "number"); + primitiveMap.put(short.class, "integer"); + primitiveMap.put(byte.class, "integer"); + primitiveMap.put(boolean.class, "boolean"); + primitiveMap.put(char.class, "string"); + primitiveMap.put(Integer.class, "integer"); + primitiveMap.put(Long.class, "integer"); + primitiveMap.put(Float.class, "number"); + primitiveMap.put(Double.class, "number"); + primitiveMap.put(Short.class, "integer"); + primitiveMap.put(Byte.class, "integer"); + primitiveMap.put(Boolean.class, "boolean"); + primitiveMap.put(Character.class, "string"); } - // edit these to adjust environments - private String[][] serverEndpoints = new String[][] { { "http://170.2.107.118:3333/api", "dev" }, - { "http://qdtmeutelcebl03.azure-qa-eastus.us164.corpintra.net:3333/api", "qaEast" }, - { "http://stnascvdl009.us164.corpintra.net:2500/proxy?target=http://pdtmeutelcebl02.azure-prd-eastus.us164.corpintra.net:3333/api", "prodEast" } }; - - public JsonObject createHeaders() { - JsonObject obj = new JsonObject(); - - // swagger version - obj.addProperty("openapi", "3.0.0"); - - // info - obj.add("info", - new JsonParser().parse("{" + "\"description\": \"Entity Broker Auto-generated documentation\"," + "\"title\": \"Entity Broker API\"," + "\"version\": \"1.0.0\"" + "}")); - - // host - JsonArray servers = new JsonArray(); - for (String[] s : serverEndpoints) { - JsonObject temp = new JsonObject(); - temp.addProperty("url", s[0]); - temp.addProperty("description", s[1]); - servers.add(temp); + + public static String toPrimitive(Class clazz) { + if (primitiveMap.containsKey(clazz)) { + return primitiveMap.get(clazz); + } else { + return "string"; } - obj.add("servers", servers); - - // schemes - // obj.add("schemes", new JsonParser().parse("[\"https\"]")); - - return obj; } - public Map getSwagger() throws IOException { - JsonObject data = createHeaders(); - - /* - * ClassPath cp = - * ClassPath.from(Thread.currentThread().getContextClassLoader()); - * Class[] classes = - * cp.getTopLevelClasses("com.daimler.eb.endpoint").asList().stream().map(( - * c) -> c.load()).toArray(Class[]::new); - */ - Class[] classes = new Class[] { Runtime.class, Servo.class }; - data.add("paths", getPaths(classes)); - - // convert to a Map - return new Gson().fromJson(data.toString(), new HashMap().getClass()); - } - - public JsonObject getPaths(Class[] classes) { - // get all the methods - Method[] methods = Arrays.stream(classes).parallel().map(c -> c.getDeclaredMethods()).reduce(new Method[] {}, - (x, y) -> Stream.concat(Arrays.stream(x), Arrays.stream(y)).toArray(Method[]::new)); - - // filter out all non-inherited and non-public methods and map each - // method to a JSON Object describing its GET / POST - /* - * { "get":{ "path":..., ... }, "post":{ "path":..., ... }, } - */ - JsonObject[] methodDetails = Arrays.stream(methods).parallel() - .filter(m -> Modifier.isPublic(m.getModifiers()) && !m.getName().equals("main") && !Modifier.isStatic(m.getModifiers())).map((m) -> { - JsonObject get = new JsonObject(); - JsonObject post = new JsonObject(); - - // construct the api path - Parameter[] params = m.getParameters(); - String paramNames = ""; - for (Parameter p : params) - paramNames += "/{" + p.getName() + "}"; - - String apiPathGet = "/" + m.getDeclaringClass().getSimpleName() + "/" + m.getName() + paramNames; - String apiPathPost = "/" + m.getDeclaringClass().getSimpleName() + "/" + m.getName(); - - // get properties & map each parameter t a JsonObject - get.addProperty("apiPath", apiPathGet); - get.add("tags", new JsonParser().parse("[" + m.getDeclaringClass().getSimpleName() + "]")); - get.add("responses", new JsonParser().parse("{" + "\"200\": {" + "\"description\": \"successful response\"" + "}" + "}")); - JsonObject[] paramDetails = Arrays.stream(params).parallel().map((p) -> { - JsonObject obj = new JsonObject(); - obj.addProperty("name", p.getName()); - obj.addProperty("in", "path"); - obj.addProperty("required", true); - obj.add("schema", getSchemaFromType(p.getType().getSimpleName())); - return obj; - }).toArray(JsonObject[]::new); - JsonArray parameters = new JsonArray(); - for (JsonObject pd : paramDetails) - parameters.add(pd); - get.add("parameters", parameters); - - // post properties - post.addProperty("apiPath", apiPathPost); - post.add("tags", new JsonParser().parse("[" + m.getDeclaringClass().getSimpleName() + "]")); - post.add("responses", new JsonParser().parse("{" + "\"200\": {" + "\"description\": \"successful response\"" + "}" + "}")); - - JsonObject requestBody = new JsonObject(); - requestBody.addProperty("required", true); - JsonObject requestBodyContent = new JsonObject(); - requestBody.add("content", requestBodyContent); - JsonObject rbcAppJson = new JsonObject(); - requestBodyContent.add("application/json", rbcAppJson); - JsonObject postBodySchema = new JsonObject(); - postBodySchema.addProperty("type", "array"); - postBodySchema.addProperty("minItems", params.length); - postBodySchema.addProperty("maxItems", params.length); - JsonObject arrayItems = new JsonObject(); - JsonArray types = new JsonArray(); - for (Parameter p : params) { - types.add(getSchemaFromType(p.getType().getSimpleName())); - } - arrayItems.add("oneOf", types); - postBodySchema.add("items", arrayItems); - rbcAppJson.add("schema", postBodySchema); - post.add("requestBody", requestBody); - - JsonObject res = new JsonObject(); - res.add("get", get); - res.add("post", post); - return res; - }).toArray(JsonObject[]::new); - - // convert each method get/post info to distinct get/post infos - JsonObject res = new JsonObject(); - for (JsonObject m : methodDetails) { - // if both GET/POST have same path, then combine into one object - if (m.get("get").getAsJsonObject().get("apiPath").equals(m.get("post").getAsJsonObject().get("apiPath"))) { - res.add(m.get("get").getAsJsonObject().get("apiPath").getAsString(), m); - } else { // otherwise, separate them into different objects - JsonObject g = new JsonObject(); - g.add("get", m.get("get")); - res.add(m.get("get").getAsJsonObject().get("apiPath").getAsString(), g); - - JsonObject p = new JsonObject(); - p.add("post", m.get("post")); - res.add(m.get("post").getAsJsonObject().get("apiPath").getAsString(), p); + public static String generateSwaggerYaml(Class clazz) { + Map swaggerData = new TreeMap<>(); + Map paths = new TreeMap<>(); + + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (Modifier.isPublic(method.getModifiers())) { + String prefix = "/" + method.getName(); // Method name is used as the path + String path = prefix; + + Class[] parameterTypes = method.getParameterTypes(); + // Parameter names are only available if compiled with -parameters + Parameter[] params = method.getParameters(); + + List> paramArray = new ArrayList<>(); + + + for (int i = 0; i < parameterTypes.length; i++) { + Parameter param = params[i]; + String paramName = "param" + i; // Using param0, param1, etc. as path + // variable names + // only available with compiler -parameters option + paramName = param.getName(); + + Map paramDetail = new TreeMap<>(); + + paramDetail.put("in", "path"); + paramDetail.put("name", paramName); + paramDetail.put("required", true); + MapparamSchema = new TreeMap<>(); + paramDetail.put("schema", paramSchema); + paramSchema.put("type", toPrimitive(param.getType())); + + paramArray.add(paramDetail); + path = path + "/{"+paramName+"}"; + } + + // Create the path entry for the method + Map data = new TreeMap<>(); + + Map get = new TreeMap<>(); + data.put("get", get); + List tags = new ArrayList<>(); + get.put("tags", tags); + tags.add(clazz.getSimpleName()); + get.put("parameters", paramArray); + Map responses = new TreeMap<>(); + get.put("responses", responses); + Map http_200 = new TreeMap<>(); + responses.put("200", http_200); + http_200.put("description", "OK"); + Map content = new TreeMap<>(); + Map json = new TreeMap<>(); + http_200.put("content", content); + content.put("application/json", json); + Map schema = new TreeMap<>(); + json.put("schema", schema); + + + paths.put(path, data); } - - m.get("get").getAsJsonObject().remove("apiPath"); - m.get("post").getAsJsonObject().remove("apiPath"); } - return res; + swaggerData.put("paths", paths); + + // Convert to YAML + Yaml yaml = new Yaml(getDumperOptions()); + return yaml.dump(swaggerData); } - private JsonObject getSchemaFromType(String s) { - JsonObject res = new JsonObject(); - s = s.toLowerCase(); - if (s.equals("String")) - res.addProperty("type", "string"); - else if (s.equals("int")) - res.addProperty("type", "integer"); - else if (s.equals("boolean")) - res.addProperty("type", "boolean"); - else if (s.equals("double") || s.equals("float") || s.equals("long") || s.equals("byte")) - res.addProperty("type", "number"); - else if (s.contains("[]")) { - res.addProperty("type", "array"); - res.add("items", getSchemaFromType(s.substring(0, s.length() - 2))); - } else - res.addProperty("type", "object"); - - return res; + private static DumperOptions getDumperOptions() { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + return options; } public static void main(String[] args) { try { - List myList = Arrays.asList("a1", "a2", "b1", "c2", "c1"); - - myList.stream() - // .filter(s -> s.startsWith("c")) - .map(String::toUpperCase).map(s -> s + "blah").map(s -> s + "oink").sorted().forEach(System.out::println); - - ApiSwagger swagger = new ApiSwagger(); - - log.info("{}", CodecUtils.toJson(swagger.getSwagger())); + + // log.info("{}", CodecUtils.toJson(ApiSwagger.generateSwaggerYaml(Servo.class))); + log.info("{}", ApiSwagger.generateSwaggerYaml(Servo.class)); } catch (Exception e) { log.error("main threw", e); } } - public Object process(MessageSender webgui, String apiKey, String uri, String uuid, OutputStream out, String json) throws Exception { - // TODO Auto-generated method stub - return null; - } - } diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index d1b50fa6fc..77756b5e2d 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -1,6 +1,5 @@ package org.myrobotlab.codec; - import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; @@ -29,7 +28,6 @@ import java.util.Set; import java.util.stream.Collectors; -import org.myrobotlab.codec.json.GsonPolymorphicTypeAdapterFactory; import org.myrobotlab.codec.json.JacksonPolymorphicModule; import org.myrobotlab.codec.json.JacksonPrettyPrinter; import org.myrobotlab.codec.json.JsonDeserializationException; @@ -62,1499 +60,1485 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.module.noctordeser.NoCtorDeserModule; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.internal.LinkedTreeMap; /** * handles all encoding and decoding of MRL messages or api(s) assumed context - * services can add an assumed context as a prefix * /api/returnEncoding/inputEncoding/service/method/param1/param2/ ... *

    - * xmpp for example assumes (/api/string/gson)/service/method/param1/param2/ ... + * xmpp for example assumes (/api/string/json)/service/method/param1/param2/ ... *

    * scheme = alpha *( alpha | digit | "+" | "-" | "." ) Components of all URIs: [ * <scheme>:]<scheme-specific-part>[#<fragment>] *

    * branch API test 5 * - * @see Valid characters for URI schemes + * @see Valid + * characters for URI schemes */ public class CodecUtils { - public final static Logger log = LoggerFactory.getLogger(CodecUtils.class); - /** - * The string to be used to specify the API in URIs, - * with leading and trailing slash. - */ - public final static String PARAMETER_API = "/api/"; - /** - * The string to be used in URIs to specify the API - */ - public final static String PREFIX_API = "api"; - /** - * The MIME type used to specify JSON data. - * - * @see MIME types - */ - public final static String MIME_TYPE_JSON = "application/json"; + public final static Logger log = LoggerFactory.getLogger(CodecUtils.class); + /** + * The string to be used to specify the API in URIs, with leading and trailing + * slash. + */ + public final static String PARAMETER_API = "/api/"; + /** + * The string to be used in URIs to specify the API + */ + public final static String PREFIX_API = "api"; + /** + * The MIME type used to specify JSON data. + * + * @see MIME + * types + */ + public final static String MIME_TYPE_JSON = "application/json"; - // mime-types - /** - * Whether we are using the GSON JSON backend or not. If false, - * use Jackson. - * TODO Replace with enum to allow extension for multiple backends - */ - public static final boolean USING_GSON = false; - /** - * The key used to locate type information - * in a JSON dictionary. This is used to serialize - * type information into the JSON and to deserialize - * JSON into the correct type. - */ - public static final String CLASS_META_KEY = "class"; - /** - * Set of all known wrapper types, which are classes that correspond to - * Java primitives (plus {@link Void}). - * - * @see Java Wrapper Classes - */ - public static final Set> WRAPPER_TYPES = new HashSet<>( - Arrays.asList(Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, - Float.class, Double.class, Void.class)); - /** - * Set of the fully-qualified (AKA canonical) names of {@link #WRAPPER_TYPES}. - * - * @see Java Wrapper Classes - */ - public static final Set WRAPPER_TYPES_CANONICAL = - WRAPPER_TYPES.stream().map(Object::getClass).map(Class::getCanonicalName).collect(Collectors.toSet()); - public static final String API_MESSAGES = "messages"; - public static final String API_SERVICE = "service"; + /** + * The key used to locate type information in a JSON dictionary. This is used + * to serialize type information into the JSON and to deserialize JSON into + * the correct type. + */ + public static final String CLASS_META_KEY = "class"; + /** + * Set of all known wrapper types, which are classes that correspond to Java + * primitives (plus {@link Void}). + * + * @see Java + * Wrapper Classes + */ + public static final Set> WRAPPER_TYPES = new HashSet<>( + Arrays.asList(Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class)); + /** + * Set of the fully-qualified (AKA canonical) names of {@link #WRAPPER_TYPES}. + * + * @see Java + * Wrapper Classes + */ + public static final Set WRAPPER_TYPES_CANONICAL = WRAPPER_TYPES.stream().map(Object::getClass).map(Class::getCanonicalName).collect(Collectors.toSet()); + public static final String API_MESSAGES = "messages"; + public static final String API_SERVICE = "service"; - /** - * The path from a top-level URL to the messages API - * endpoint. - *

    - * FIXME This should be moved to WebGui, - * CodecUtils should have no knowledge of URLs - */ - public static final String API_MESSAGES_PATH = PARAMETER_API + API_MESSAGES; + /** + * The path from a top-level URL to the messages API endpoint. + *

    + *

    + * FIXME This should be moved to WebGui, CodecUtils should have no knowledge + * of URLs + */ + public static final String API_MESSAGES_PATH = PARAMETER_API + API_MESSAGES; + /** + * The path from a top-level URL to the service API endpoint. + *

    + *

    + * FIXME This should be moved to WebGui, CodecUtils should have no knowledge + * of URLs + */ + public static final String API_SERVICE_PATH = PARAMETER_API + API_SERVICE; + /** + * use {@link MethodCache} + */ + @Deprecated + final static HashMap methodCache = new HashMap(); + /** + * a method signature map based on name and number of methods - the String[] + * will be the keys into the methodCache A method key is generated by input + * from some encoded protocol - the method key is object name + method name + + * parameter number - this returns a full method signature key which is used + * to look up the method in the methodCache + */ + final static HashMap> methodOrdinal = new HashMap>(); + final static HashSet objectsCached = new HashSet(); + /** + * Equivalent to {@link #MIME_TYPE_JSON} + */ + @Deprecated + static final String JSON = "application/javascript"; - /** - * The path from a top-level URL to the service API - * endpoint. - *

    - * FIXME This should be moved to WebGui, - * CodecUtils should have no knowledge of URLs - */ - public static final String API_SERVICE_PATH = PARAMETER_API + API_SERVICE; - /** - * use {@link MethodCache} - */ - @Deprecated - final static HashMap methodCache = new HashMap(); - /** - * a method signature map based on name and number of methods - the String[] - * will be the keys into the methodCache A method key is generated by input - * from some encoded protocol - the method key is object name + method name + - * parameter number - this returns a full method signature key which is used - * to look up the method in the methodCache - */ - final static HashMap> methodOrdinal = new HashMap>(); - final static HashSet objectsCached = new HashSet(); - /** - * Equivalent to {@link #MIME_TYPE_JSON} - */ - @Deprecated - static final String JSON = "application/javascript"; - /** - * The type that GSON uses when it attempts to deserialize - * without knowing the target type, e.g. if the target - * is {@link Object}. - */ - private static final Class GSON_DEFAULT_OBJECT_TYPE = LinkedTreeMap.class; - /** - * The type that Jackson uses when it attempts to deserialize - * without knowing the target type, e.g. if the target - * is {@link Object} and no field matching {@link #CLASS_META_KEY} - * is found. - */ - private static final Class JACKSON_DEFAULT_OBJECT_TYPE = LinkedHashMap.class; - /** - * The type that the chosen JSON backend uses when it attempts to deserialize - * without knowing the target type, e.g. if the target - * is {@link Object} and no field matching {@link #CLASS_META_KEY} - * is found. - */ - public static final Class JSON_DEFAULT_OBJECT_TYPE = (USING_GSON) ? GSON_DEFAULT_OBJECT_TYPE : JACKSON_DEFAULT_OBJECT_TYPE; - - /** - * Default type for single parameter fromJson(String json), we initially assume this type - */ - public static final Class DEFAULT_OBJECT_TYPE = LinkedHashMap.class; - - - /** - * The {@link Gson} object used for JSON operations when the selected backend is - * Gson. - * - * @see #USING_GSON - */ - private static final Gson gson = new GsonBuilder().registerTypeAdapterFactory(new GsonPolymorphicTypeAdapterFactory()) - .setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").serializeNulls().disableHtmlEscaping().create(); - /** - * The {@link Gson} object used to pretty-print JSON. - */ - private static final Gson prettyGson = new GsonBuilder().registerTypeAdapterFactory(new GsonPolymorphicTypeAdapterFactory()) - .setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").setPrettyPrinting().disableHtmlEscaping().create(); - /** - * The Jackson {@link ObjectMapper} used for JSON operations when - * the selected backend is Jackson. - * - * @see #USING_GSON - */ - private static final ObjectMapper mapper = new ObjectMapper(); + /** + * The type that Jackson uses when it attempts to deserialize without knowing + * the target type, e.g. if the target is {@link Object} and no field matching + * {@link #CLASS_META_KEY} is found. + */ + private static final Class JACKSON_DEFAULT_OBJECT_TYPE = LinkedHashMap.class; + /** + * The type that the chosen JSON backend uses when it attempts to deserialize + * without knowing the target type, e.g. if the target is {@link Object} and + * no field matching {@link #CLASS_META_KEY} is found. + */ + public static final Class JSON_DEFAULT_OBJECT_TYPE = JACKSON_DEFAULT_OBJECT_TYPE; + /** + * Default type for single parameter fromJson(String json), we initially + * assume this type + */ + public static final Class DEFAULT_OBJECT_TYPE = LinkedHashMap.class; - /** - * The pretty printer to be used with {@link #mapper} - * when {@link #USING_GSON} equals false, i.e. we're using Jackson. - */ - private static final PrettyPrinter jacksonPrettyPrinter = new JacksonPrettyPrinter(); + /** + * The Jackson {@link ObjectMapper} used for JSON operations when the selected + * backend is Jackson. + * + */ + private static final ObjectMapper mapper = new ObjectMapper(); - /** - * The {@link TypeFactory} used to generate type information for - * {@link #mapper} when the selected backend is Jackson. - *

    - * No analogue exists for Gson, as it uses a different mechanism - * to represent types. - * - * @see #USING_GSON - */ - private static final TypeFactory typeFactory = TypeFactory.defaultInstance(); + /** + * The pretty printer to be used with {@link #mapper} + */ + private static final PrettyPrinter jacksonPrettyPrinter = new JacksonPrettyPrinter(); - //Class initializer to setup mapper when the class is loaded - static { - //This allows Jackson to work just like GSON when no default constructor is available - mapper.registerModule(new NoCtorDeserModule()); + /** + * The {@link TypeFactory} used to generate type information for + * {@link #mapper} when the selected backend is Jackson. + *

    + */ + private static final TypeFactory typeFactory = TypeFactory.defaultInstance(); - SimpleModule proxySerializerModule = new SimpleModule(); - proxySerializerModule.addSerializer(Proxy.class, new ProxySerializer()); - mapper.registerModule(proxySerializerModule); + // Class initializer to setup mapper when the class is loaded + static { + // This allows Jackson to when no default constructor is + // available + mapper.registerModule(new NoCtorDeserModule()); - //Actually add our polymorphic support - mapper.registerModule(JacksonPolymorphicModule.getPolymorphicModule()); + SimpleModule proxySerializerModule = new SimpleModule(); + proxySerializerModule.addSerializer(Proxy.class, new ProxySerializer()); + mapper.registerModule(proxySerializerModule); - //Disables Jackson's automatic property detection - mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); -// mapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY); - mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + // Actually add our polymorphic support + mapper.registerModule(JacksonPolymorphicModule.getPolymorphicModule()); - //Make jackson behave like gson in that unknown properties are ignored - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - } + // Disables Jackson's automatic property detection + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + // mapper.setVisibility(PropertyAccessor.SETTER, + // JsonAutoDetect.Visibility.PUBLIC_ONLY); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - /** - * Ensures a service type name is fully qualified. - * If the type name is short, it will assume the type - * exists in the {@code org.myrobotlab.service} package. - * - * @param type The service type name, either shortened or - * fully qualified. - * @return Null if type is null, otherwise fully qualified name. - */ - public static String makeFullTypeName(String type) { - if (type == null) { - return null; - } - if (!type.contains(".")) { - return String.format("org.myrobotlab.service.%s", type); - } - return type; - } + // Make jackson behave such that unknown properties are ignored + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); - /** - * Capitalize the first character of the given string - * - * @param line The string to be capitalized - * @return The capitalized version of line. - */ - public static String capitalize(final String line) { - return Character.toUpperCase(line.charAt(0)) + line.substring(1); - } + } - /** - * Deserializes a JSON string into the target object - * (or subclass of if {@link #CLASS_META_KEY} exists) - * using the selected JSON backend. - * - * @param json The JSON to be deserialized in String form - * @param clazz The target class. If a class is not supplied the default class returned will be an {@link #DEFAULT_OBJECT_TYPE} - * @param The type of the target class. - * @return An object of the specified class (or a subclass of) with the state - * given by the json. Null is an allowed return object. - * @throws JsonDeserializationException if an error during deserialization occurs. - * @see #USING_GSON - */ - @SuppressWarnings("unchecked") - public static /*@Nullable*/ T fromJson(/*@Nonnull*/ String json, /*@Nonnull*/ Class clazz) { - try { - if (USING_GSON) { - if (clazz == null) { - clazz = (Class)DEFAULT_OBJECT_TYPE; - } - return gson.fromJson(json, clazz); - } else { - if (clazz == null) { - - JsonParser parser = mapper.getFactory().createParser(json); - - // "peek" at the next token to determine its type - JsonToken token = parser.nextToken(); - - if (token == JsonToken.START_OBJECT) { - clazz = (Class)Map.class; - } else if (token == JsonToken.START_ARRAY) { - clazz = (Class)ArrayList.class; - } else if (token.isScalarValue()) { - JsonNode node = mapper.readTree(json); - if (node.isInt()) { - return mapper.readValue(json, (Class)Integer.class); - } else if (node.isBoolean()) { - return mapper.readValue(json, (Class)Boolean.class); - } else if (node.isNumber()) { - return mapper.readValue(json, (Class)Double.class); - } else if (node.isTextual()) { - return mapper.readValue(json, (Class)String.class); - } - } else { - log.error("could not derive type from peeking json {}", json); - } - - parser.close(); - - } - return mapper.readValue(json, clazz); - } - } catch (Exception e) { - throw new JsonDeserializationException(e); - } + /** + * Ensures a service type name is fully qualified. If the type name is short, + * it will assume the type exists in the {@code org.myrobotlab.service} + * package. + * + * @param type + * The service type name, either shortened or fully qualified. + * @return Null if type is null, otherwise fully qualified name. + */ + public static String makeFullTypeName(String type) { + if (type == null) { + return null; } + if (!type.contains(".")) { + return String.format("org.myrobotlab.service.%s", type); + } + return type; + } - /** - * Deserializes a JSON string into the target object - * (or subclass of if {@link #CLASS_META_KEY} exists) - * using the selected JSON backend. - * - * @param json The JSON to be deserialized in String form - * @param genericClass The target class. - * @param parameterized The list of types used as the genericClass type parameters - * of genericClass. - * @param The type of the target class. - * @return An object of the specified class (or a subclass of) with the state - * given by the json. Null is an allowed return object. - * @throws JsonDeserializationException if an error during deserialization occurs. - * @see #USING_GSON - */ - public static /*@Nullable*/ T fromJson(/*@Nonnull*/ String json, /*@Nonnull*/ Class genericClass, - /*@Nonnull*/ Class... parameterized) { - try { - if (USING_GSON) { - return gson.fromJson(json, getType(genericClass, parameterized)); - } - - return mapper.readValue(json, typeFactory.constructParametricType(genericClass, parameterized)); - } catch (Exception e) { - throw new JsonDeserializationException(e); + /** + * Capitalize the first character of the given string + * + * @param line + * The string to be capitalized + * @return The capitalized version of line. + */ + public static String capitalize(final String line) { + return Character.toUpperCase(line.charAt(0)) + line.substring(1); + } + + /** + * Deserializes a JSON string into the target object (or subclass of if + * {@link #CLASS_META_KEY} exists) using the selected JSON backend. + * + * @param json + * The JSON to be deserialized in String form + * @param clazz + * The target class. If a class is not supplied the default class + * returned will be an {@link #DEFAULT_OBJECT_TYPE} + * @param + * The type of the target class. + * @return An object of the specified class (or a subclass of) with the state + * given by the json. Null is an allowed return object. + * @throws JsonDeserializationException + * if an error during deserialization occurs. + */ + @SuppressWarnings("unchecked") + public static /* @Nullable */ T fromJson(/* @Nonnull */ String json, + /* @Nonnull */ Class clazz) { + try { + if (clazz == null) { + + JsonParser parser = mapper.getFactory().createParser(json); + + // "peek" at the next token to determine its type + JsonToken token = parser.nextToken(); + + if (token == JsonToken.START_OBJECT) { + clazz = (Class) Map.class; + } else if (token == JsonToken.START_ARRAY) { + clazz = (Class) ArrayList.class; + } else if (token.isScalarValue()) { + JsonNode node = mapper.readTree(json); + if (node.isInt()) { + return mapper.readValue(json, (Class) Integer.class); + } else if (node.isBoolean()) { + return mapper.readValue(json, (Class) Boolean.class); + } else if (node.isNumber()) { + return mapper.readValue(json, (Class) Double.class); + } else if (node.isTextual()) { + return mapper.readValue(json, (Class) String.class); + } + } else { + log.error("could not derive type from peeking json {}", json); } + + parser.close(); + + } + return mapper.readValue(json, clazz); + } catch (Exception e) { + throw new JsonDeserializationException(e); } + } - /** - * Deserializes a json string into the type represented by - * {@link T}. {@code type} must must match {@link T} exactly, - * otherwise the deserializers may not deserialize into T. - * - * @param json A string encoded in JSON - * @param type Reified type information to pass to the deserializers - * @return An instance of T decoded from the json - * @param The type to deserialize into - * @throws JsonDeserializationException if the selected deserializer throws an exception - */ - public static /*@Nullable*/ T fromJson(/*@NonNull*/ String json, /*@NonNull*/ StaticType type) { - return fromJson(json, type.getType()); + /** + * Deserializes a JSON string into the target object (or subclass of if + * {@link #CLASS_META_KEY} exists) using the selected JSON backend. + * + * @param json + * The JSON to be deserialized in String form + * @param genericClass + * The target class. + * @param parameterized + * The list of types used as the genericClass type parameters of + * genericClass. + * @param + * The type of the target class. + * @return An object of the specified class (or a subclass of) with the state + * given by the json. Null is an allowed return object. + * @throws JsonDeserializationException + * if an error during deserialization occurs. + */ + public static /* @Nullable */ T fromJson(/* @Nonnull */ String json, + /* @Nonnull */ Class genericClass, /* @Nonnull */ Class... parameterized) { + try { + return mapper.readValue(json, typeFactory.constructParametricType(genericClass, parameterized)); + } catch (Exception e) { + throw new JsonDeserializationException(e); } + } - /** - * Deserializes a JSON string into the target object - * (or subclass of if {@link #CLASS_META_KEY} exists) - * using the selected JSON backend. - * - * @param json The JSON to be deserialized in String form - * @param type The target type. - * @param The type of the target class. - * @return An object of the specified class (or a subclass of) with the state - * given by the json. Null is an allowed return object. - * @throws JsonDeserializationException if an error during deserialization occurs. - * @see #USING_GSON - */ - public static /*@Nullable*/ T fromJson(/*@Nonnull*/ String json, /*@Nonnull*/ Type type) { - try { - if (USING_GSON) { - return gson.fromJson(json, type); - } - return mapper.readValue(json, typeFactory.constructType(type)); - } catch (Exception e) { - throw new JsonDeserializationException(e); - } + /** + * Deserializes a json string into the type represented by {@link T}. + * {@code type} must must match {@link T} exactly, otherwise the deserializers + * may not deserialize into T. + * + * @param json + * A string encoded in JSON + * @param type + * Reified type information to pass to the deserializers + * @return An instance of T decoded from the json + * @param + * The type to deserialize into + * @throws JsonDeserializationException + * if the selected deserializer throws an exception + */ + public static /* @Nullable */ T fromJson(/* @NonNull */ String json, + /* @NonNull */ StaticType type) { + return fromJson(json, type.getType()); + } + + /** + * Deserializes a JSON string into the target object (or subclass of if + * {@link #CLASS_META_KEY} exists) using the selected JSON backend. + * + * @param json + * The JSON to be deserialized in String form + * @param type + * The target type. + * @param + * The type of the target class. + * @return An object of the specified class (or a subclass of) with the state + * given by the json. Null is an allowed return object. + * @throws JsonDeserializationException + * if an error during deserialization occurs. + */ + public static /* @Nullable */ T fromJson(/* @Nonnull */ String json, + /* @Nonnull */ Type type) { + try { + return mapper.readValue(json, typeFactory.constructType(type)); + } catch (Exception e) { + throw new JsonDeserializationException(e); } + } - /** - * Convert the given JSON string - * into an equivalent tree map. - * - * @param json The json to be converted - * @return The json in a tree map form - * @throws JsonDeserializationException if deserialization fails - */ - @SuppressWarnings("unchecked") - public static LinkedTreeMap toTree(String json) { - try { - if (USING_GSON) { - return gson.fromJson(json, LinkedTreeMap.class); - } - return (LinkedTreeMap) mapper.readValue(json, LinkedTreeMap.class); - } catch (Exception e) { - throw new JsonDeserializationException(e); - } + /** + * Convert the given JSON string into an equivalent tree map. + * + * @param json + * The json to be converted + * @return The json in a tree map form + * @throws JsonDeserializationException + * if deserialization fails + */ + @SuppressWarnings("unchecked") + public static LinkedHashMap toTree(String json) { + try { + return (LinkedHashMap) mapper.readValue(json, LinkedHashMap.class); + } catch (Exception e) { + throw new JsonDeserializationException(e); } + } - public static Type getType(final Class rawClass, final Class... parameterClasses) { - return new ParameterizedType() { - @Override - public Type[] getActualTypeArguments() { - return parameterClasses; - } + public static Type getType(final Class rawClass, final Class... parameterClasses) { + return new ParameterizedType() { + @Override + public Type[] getActualTypeArguments() { + return parameterClasses; + } - @Override - public Type getRawType() { - return rawClass; - } + @Override + public Type getRawType() { + return rawClass; + } - @Override - public Type getOwnerType() { - return null; - } + @Override + public Type getOwnerType() { + return null; + } - }; - } + }; + } - static public byte[] getBytes(Object o) throws IOException { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(5000); - ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(byteStream)); - os.flush(); - os.writeObject(o); - os.flush(); - return byteStream.toByteArray(); - } + static public byte[] getBytes(Object o) throws IOException { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(5000); + ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(byteStream)); + os.flush(); + os.writeObject(o); + os.flush(); + return byteStream.toByteArray(); + } - /** - * Gets the short name of a service. A - * short name is the name of the service without - * any runtime IDs, meaning no '@' signs. - * - * @param name The service name to be converted - * @return The simple name of the service. If null, - * will return null, and if already a simple name - * then will return name - */ - static public String getShortName(String name) { - if (name == null) { - return null; - } - if (name.contains("@")) { - return name.substring(0, name.indexOf("@")); - } else { - return name; - } + /** + * Gets the short name of a service. A short name is the name of the service + * without any runtime IDs, meaning no '@' signs. + * + * @param name + * The service name to be converted + * @return The simple name of the service. If null, will return null, and if + * already a simple name then will return name + */ + static public String getShortName(String name) { + if (name == null) { + return null; + } + if (name.contains("@")) { + return name.substring(0, name.indexOf("@")); + } else { + return name; } - + } - // TODO - // public static Object encode(Object, encoding) - dispatches appropriately - //should be simple using an enum and map to a new Encoder functional interface + // TODO + // public static Object encode(Object, encoding) - dispatches appropriately + // should be simple using an enum and map to a new Encoder functional + // interface - /** - * Gets the instance id from a service name - * - * @param name the name of the instance - * @return the name of the instance - */ - static public String getId(String name) { - if (name == null) { - return null; - } - if (name.contains("@")) { - return name.substring(name.lastIndexOf("@") + 1); - } else { - return null; - } + /** + * Gets the instance id from a service name + * + * @param name + * the name of the instance + * @return the name of the instance + */ + static public String getId(String name) { + if (name == null) { + return null; } + if (name.contains("@")) { + return name.substring(name.lastIndexOf("@") + 1); + } else { + return null; + } + } - /** - * Normalizes a service name to its full - * name, if not already. A full name consists of - * two parts: the short name that identifies the service - * in the context of its own runtime, and the Runtime ID, - * that identifies the service's runtime from others in the network. - * The two parts are separated by an {@code @} symbol. - *

    - * If this method is given a short name, it is assumed to be local - * to this runtime, and it is normalized with the ID of this runtime. - * If the name is already a full name, then it is returned unmodified. - * @param name The service name to normalize - * @return The normalized (full) name, or null if name is null - */ - public static String getFullName(String name) { - if (name == null) { - return null; - } + /** + * Normalizes a service name to its full name, if not already. A full name + * consists of two parts: the short name that identifies the service in the + * context of its own runtime, and the Runtime ID, that identifies the + * service's runtime from others in the network. The two parts are separated + * by an {@code @} symbol. + *

    + * If this method is given a short name, it is assumed to be local to this + * runtime, and it is normalized with the ID of this runtime. If the name is + * already a full name, then it is returned unmodified. + * + * @param name + * The service name to normalize + * @return The normalized (full) name, or null if name is null + */ + public static String getFullName(String name) { + if (name == null) { + return null; + } - if (getId(name) == null) { - return name + '@' + Platform.getLocalInstance().getId(); - } else { - return name; - } + if (getId(name) == null) { + return name + '@' + Platform.getLocalInstance().getId(); + } else { + return name; } + } - /** - * Checks whether two service names are equal by first normalizing - * each. If a name does not have a runtime ID, it is assumed to be - * a local service. - * @param name1 The first service name - * @param name2 The second service name - * @return Whether the two names are effectively equal - */ - public static boolean checkServiceNameEquality(String name1, String name2) { - return Objects.equals(getFullName(name1), getFullName(name2)); + /** + * Checks whether two service names are equal by first normalizing each. If a + * name does not have a runtime ID, it is assumed to be a local service. + * + * @param name1 + * The first service name + * @param name2 + * The second service name + * @return Whether the two names are effectively equal + */ + public static boolean checkServiceNameEquality(String name1, String name2) { + return Objects.equals(getFullName(name1), getFullName(name2)); + } + + /** + * Converts a topic method name to the name of the method that is used for + * callbacks. Usually this involves prepending the string "on", removing any + * "get" or "publish" prefix, and converting it all to proper camelCase. + * + * @param topicMethod + * The topic method name, such as "publishState" + * @return The name for the callback method, such as "onState" + */ + static public String getCallbackTopicName(String topicMethod) { + // replacements + if (topicMethod.startsWith("publish")) { + return String.format("on%s", capitalize(topicMethod.substring("publish".length()))); + } else if (topicMethod.startsWith("get")) { + return String.format("on%s", capitalize(topicMethod.substring("get".length()))); } - /** - * Converts a topic method name to the name of the method that is - * used for callbacks. Usually this involves prepending the string - * "on", removing any "get" or "publish" prefix, and converting - * it all to proper camelCase. - * - * @param topicMethod The topic method name, such as "publishState" - * @return The name for the callback method, such as "onState" - */ - static public String getCallbackTopicName(String topicMethod) { - // replacements - if (topicMethod.startsWith("publish")) { - return String.format("on%s", capitalize(topicMethod.substring("publish".length()))); - } else if (topicMethod.startsWith("get")) { - return String.format("on%s", capitalize(topicMethod.substring("get".length()))); - } + // no replacement - just pefix and capitalize + // FIXME - subscribe to onMethod --- gets ---> onOnMethod :P + return String.format("on%s", capitalize(topicMethod)); + } - // no replacement - just pefix and capitalize - // FIXME - subscribe to onMethod --- gets ---> onOnMethod :P - return String.format("on%s", capitalize(topicMethod)); + /** + * Gets a String representation of a Message + * + * @param msg + * The message + * @return The String representation of the message + */ + static public String getMsgKey(Message msg) { + if (msg.sendingMethod != null) { + return String.format("%s.%s --> %s.%s(%s) - %d", msg.sender, msg.sendingMethod, msg.name, msg.method, CodecUtils.getParameterSignature(msg.data), msg.msgId); + } else { + return String.format("%s --> %s.%s(%s) - %d", msg.sender, msg.name, msg.method, CodecUtils.getParameterSignature(msg.data), msg.msgId); } + } - /** - * Gets a String representation of a Message - * - * @param msg The message - * @return The String representation of the message - */ - static public String getMsgKey(Message msg) { - if (msg.sendingMethod != null) { - return String.format("%s.%s --> %s.%s(%s) - %d", msg.sender, msg.sendingMethod, msg.name, msg.method, CodecUtils.getParameterSignature(msg.data), msg.msgId); + /** + * Get a String representing the data in method parameter form, i.e. each + * element is separated by a comma. Only {@link #WRAPPER_TYPES} and + * {@link MRLListener} will be directly converted to string form using + * {@link Object#toString()}, all other types will be represented as their + * class's simple name. + * + * @param data + * The list of objects to be represented as a parameter list string. + * @return The string representing the data array + */ + static public String getParameterSignature(final Object[] data) { + if (data == null) { + return ""; + } + + StringBuffer ret = new StringBuffer(); + for (int i = 0; i < data.length; ++i) { + if (data[i] != null) { + Class c = data[i].getClass(); // not all data types are safe + // toString() e.g. + // SerializableImage + // if (c == String.class || c == Integer.class || c == Boolean.class || + // c == Float.class || c == MRLListener.class) { + if (WRAPPER_TYPES.stream().anyMatch(n -> n.equals(c)) || MRLListener.class.equals(c)) { + ret.append(data[i].toString()); } else { - return String.format("%s --> %s.%s(%s) - %d", msg.sender, msg.name, msg.method, CodecUtils.getParameterSignature(msg.data), msg.msgId); + String type = data[i].getClass().getCanonicalName(); + String shortTypeName = type.substring(type.lastIndexOf(".") + 1); + ret.append(shortTypeName); } - } - /** - * Get a String representing the data in method parameter form, - * i.e. each element is separated by a comma. Only {@link #WRAPPER_TYPES} - * and {@link MRLListener} will be directly converted to string form using - * {@link Object#toString()}, all other types will be represented as their class's - * simple name. - * - * @param data The list of objects to be represented as a parameter list string. - * @return The string representing the data array - */ - static public String getParameterSignature(final Object[] data) { - if (data == null) { - return ""; + if (data.length != i + 1) { + ret.append(","); } + } else { + ret.append("null"); + } - StringBuffer ret = new StringBuffer(); - for (int i = 0; i < data.length; ++i) { - if (data[i] != null) { - Class c = data[i].getClass(); // not all data types are safe - // toString() e.g. - // SerializableImage - //if (c == String.class || c == Integer.class || c == Boolean.class || c == Float.class || c == MRLListener.class) { - if (WRAPPER_TYPES.stream().anyMatch(n -> n.equals(c)) || MRLListener.class.equals(c)) { - ret.append(data[i].toString()); - } else { - String type = data[i].getClass().getCanonicalName(); - String shortTypeName = type.substring(type.lastIndexOf(".") + 1); - ret.append(shortTypeName); - } - - if (data.length != i + 1) { - ret.append(","); - } - } else { - ret.append("null"); - } + } + return ret.toString(); - } - return ret.toString(); + } + static public String getServiceType(String inType) { + if (inType == null) { + return null; + } + if (inType.contains(".")) { + return inType; } + return String.format("org.myrobotlab.service.%s", inType); + } - static public String getServiceType(String inType) { - if (inType == null) { - return null; - } - if (inType.contains(".")) { - return inType; - } - return String.format("org.myrobotlab.service.%s", inType); + /** + * Deserializes a message and its data from a JSON string representation into + * a fully decoded Message object. This method will first attempt to use the + * method cache to determine what types the data elements should be + * deserialized to, and if the method cache lookup fails it relies on the + * virtual "class" field of the JSON to provide the type information. + * + * @param jsonData + * The serialized Message in JSON form + * @return A completely decoded Message object. Null is allowed if the JSON + * represented null. + * @throws JsonDeserializationException + * if jsonData is malformed + */ + public static /* @Nullable */ Message jsonToMessage(/* @Nonnull */ String jsonData) { + if (log.isDebugEnabled()) { + log.debug("Deserializing message: {}", jsonData); } + Message msg = fromJson(jsonData, Message.class); - /** - * Deserializes a message and its data from a JSON - * string representation into a fully decoded Message - * object. This method will first attempt to use the - * method cache to determine what types the data - * elements should be deserialized to, and if the method - * cache lookup fails it relies on the virtual "class" - * field of the JSON to provide the type information. - * - * @param jsonData The serialized Message in JSON form - * @return A completely decoded Message object. Null is allowed if the JSON - * represented null. - * @throws JsonDeserializationException if jsonData is malformed - */ - public static /*@Nullable*/ Message jsonToMessage(/*@Nonnull*/ String jsonData) { - if (log.isDebugEnabled()) { - log.debug("Deserializing message: {}",jsonData); - } - Message msg = fromJson(jsonData, Message.class); + if (msg == null) { + log.warn("Null message within json, probably shouldn't happen"); + return null; + } + return decodeMessageParams(msg); + } - if (msg == null) { - log.warn("Null message within json, probably shouldn't happen"); - return null; + /** + * Performs the second-stage decoding of a Message with JSON-encoded data + * parameters. This method is meant to be a helper for the top-level Message + * decoding methods to go straight from the various codecs to a completely + * decoded Message. + *

    + * Package visibility to allow alternative codecs to use this method. + *

    + *

    + *

    + *

    Implementation Details

    There are important caveats to note when + * using this method as a result of the implementation chosen. + *

    + * If the method msg invokes is contained within the {@link MethodCache}, + * there exists type information for the data parameters and they can be + * deserialized into the correct type using this method. + *

    + *

    + * However, if no such method exists within the cache this method falls back + * on using the embedded virtual meta field ({@link #CLASS_META_KEY}). Since + * there is no type information available, there are two main caveats to using + * this fallback method: + *

    + * + *
      + *
    1. Without the type information from the method cache we have no way of + * knowing whether to interpret an array as an array of Objects or as a List + * (or even what implementor of List to use)
    2. + *
    + * + * + * @param msg + * The Message object containing the json-encoded data parameters. + * This object will be modified in-place + * @return A fully-decoded Message + * @throws JsonDeserializationException + * if any of the data parameters are malformed JSON + */ + public static /* @Nonnull */ Message decodeMessageParams(/* @Nonnull */ Message msg) { + String serviceName = msg.getFullName(); + Class clazz = Runtime.getClass(serviceName); + + // Nullability of clazz is checked with this, if null + // falls back to virt class field + boolean useVirtClassField = clazz == null; + if (!useVirtClassField) { + try { + Object[] params = MethodCache.getInstance().getDecodedJsonParameters(clazz, msg.method, msg.data); + if (params == null) + useVirtClassField = true; + else { + msg.data = params; } - return decodeMessageParams(msg); + msg.encoding = null; + } catch (RuntimeException e) { + log.info(String.format("MethodCache lookup fail: %s.%s", serviceName, msg.method)); + // Fallback to virtual class field + useVirtClassField = true; + } } - /** - * Performs the second-stage decoding of a Message - * with JSON-encoded data parameters. This method is meant - * to be a helper for the top-level Message decoding methods - * to go straight from the various codecs to a completely decoded Message. - *

    - * Package visibility to allow alternative codecs to use this method. - *

    - *

    - *

    Implementation Details

    - * There are important caveats to note when using - * this method as a result of the implementation chosen. - *

    - * If the method msg invokes is contained within the - * {@link MethodCache}, there exists type information - * for the data parameters and they can be deserialized - * into the correct type using this method. - *

    - *

    - * However, if no such method exists within - * the cache this method falls back on using the - * embedded virtual meta field ({@link #CLASS_META_KEY}). - * Since there is no type information available, there are two - * main caveats to using this fallback method: - *

    - * - *
      - *
    1. GSON has edge cases for arrays of objects, since - * we don't know what type is contained within the array we are forced - * to deserialize it to an array of Objects, but GSON skips our custom - * deserializer for the Object type. So an array of objects that are not - * primitives nor Strings *will* deserialize incorrectly unless - * we are using Jackson.
    2. - *
    3. Without the type information from the method cache we have - * no way of knowing whether to interpret an array as an array of Objects - * or as a List (or even what implementor of List to use)
    4. - *
    - * - * - * @param msg The Message object containing the json-encoded data parameters. - * This object will be modified in-place - * @return A fully-decoded Message - * @throws JsonDeserializationException if any of the data parameters are malformed JSON - */ - static /*@Nonnull*/ Message decodeMessageParams(/*@Nonnull*/ Message msg) { - String serviceName = msg.getFullName(); - Class clazz = Runtime.getClass(serviceName); - - //Nullability of clazz is checked with this, if null - //falls back to virt class field - boolean useVirtClassField = clazz == null; - if (!useVirtClassField) { - try { - Object[] params = MethodCache.getInstance().getDecodedJsonParameters(clazz, msg.method, msg.data); - if (params == null) - useVirtClassField = true; - else { - msg.data = params; - } - msg.encoding = null; - } catch (RuntimeException e) { - log.info(String.format("MethodCache lookup fail: %s.%s", serviceName, msg.method)); - // Fallback to virtual class field - useVirtClassField = true; - } + // Not an else since useVirtClassField can be set in the above if block + if (useVirtClassField && msg.data != null) { + for (int i = 0; i < msg.data.length; i++) { + if (msg.data[i] instanceof String) { + + if (isBoolean((String) msg.data[i])) { + msg.data[i] = makeBoolean((String) msg.data[i]); + } else if (isInteger((String) msg.data[i])) { + msg.data[i] = makeInteger((String) msg.data[i]); + } else if (isDouble((String) msg.data[i])) { + msg.data[i] = makeDouble((String) msg.data[i]); + } else if (((String) msg.data[i]).startsWith("\"")) { + msg.data[i] = fromJson((String) msg.data[i], String.class); + } else if (((String) msg.data[i]).startsWith("[")) { + // Array, deserialize to ArrayList to maintain compat with jackson + msg.data[i] = fromJson((String) msg.data[i], ArrayList.class); + } else { + // Object + // Serializable should cover everything of interest + + msg.data[i] = fromJson((String) msg.data[i], Serializable.class); + } + + if (msg.data[i] != null && JSON_DEFAULT_OBJECT_TYPE.isAssignableFrom(msg.data[i].getClass())) { + log.warn("Deserialized parameter to default object type. " + "Possibly missing virtual class field: " + msg.data[i]); + } + } else { + log.error("Attempted fallback Message decoding with virtual class field but " + "parameter is not String: %s"); } + } + msg.encoding = null; + } - // Not an else since useVirtClassField can be set in the above if block - if (useVirtClassField) { - for (int i = 0; i < msg.data.length; i++) { - if (msg.data[i] instanceof String) { - // GSON ignores custom deserializers when going to Object - if (!USING_GSON) { - msg.data[i] = fromJson((String) msg.data[i], Object.class); - } else { - // Workaround because GSON won't deserialize a primitive when - // given serializable - if (isBoolean((String) msg.data[i])) { - msg.data[i] = makeBoolean((String) msg.data[i]); - } else if(isInteger((String) msg.data[i])) { - msg.data[i] = makeInteger((String) msg.data[i]); - } else if (isDouble((String) msg.data[i])) { - msg.data[i] = makeDouble((String) msg.data[i]); - } else if (((String) msg.data[i]).startsWith("\"")) { - msg.data[i] = fromJson((String) msg.data[i], String.class); - } else if(((String) msg.data[i]).startsWith("[")) { - // Array, deserialize to ArrayList to maintain compat with jackson - msg.data[i] = fromJson((String) msg.data[i], ArrayList.class); - } else { - // Object - // Serializable should cover everything of interest - - msg.data[i] = fromJson((String) msg.data[i], Serializable.class); - } - - } - - if (msg.data[i] != null && JSON_DEFAULT_OBJECT_TYPE.isAssignableFrom(msg.data[i].getClass())) { - log.warn("Deserialized parameter to default object type. " + - "Possibly missing virtual class field: " + - msg.data[i]); - } - } else { - log.error( - "Attempted fallback Message decoding with virtual class field but " + - "parameter is not String: %s" - ); - } - } - msg.encoding = null; - } + return msg; + } - return msg; - } + /** + * most lossy protocols need conversion of parameters into correctly typed + * elements this method is used to query a candidate method to see if a simple + * conversion is possible + * + * @param clazz + * the class + * @return true/false + */ + public static boolean isSimpleType(Class clazz) { + return WRAPPER_TYPES.contains(clazz) || clazz == String.class; + } - public static Message gsonToMsg(String gsonData) { - return gson.fromJson(gsonData, Message.class); - } + public static boolean isWrapper(Class clazz) { + return WRAPPER_TYPES.contains(clazz); + } - /** - * most lossy protocols need conversion of parameters into correctly typed - * elements this method is used to query a candidate method to see if a simple - * conversion is possible - * - * @param clazz the class - * @return true/false - */ - public static boolean isSimpleType(Class clazz) { - return WRAPPER_TYPES.contains(clazz) || clazz == String.class; + public static boolean isWrapper(String className) { + return WRAPPER_TYPES_CANONICAL.contains(className); + } + + /** + * Converts a snake_case String to a camelCase variant. + * + * @param s + * A String written in snake_case + * @return The same String but converted to camelCase + */ + static public String toCamelCase(String s) { + String[] parts = s.split("_"); + String camelCaseString = ""; + for (String part : parts) { + camelCaseString = camelCaseString + toCCase(part); } + return String.format("%s%s", camelCaseString.substring(0, 1).toLowerCase(), camelCaseString.substring(1)); + } + + /** + * Capitalizes the first character of the string while the rest is set to + * lower case. + * + * @param s + * The string + * @return A String that is all lower case except for the first character + */ + static public String toCCase(String s) { + return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + } - public static boolean isWrapper(Class clazz) { - return WRAPPER_TYPES.contains(clazz); + /** + * Convert an Object to its JSON string form using the chosen JSON backend. + * + * @param o + * The object to be converted + * @return The object in String JSON form + * @throws JsonSerializationException + * if serialization fails + */ + public static String toJson(Object o) { + try { + return mapper.writeValueAsString(o); + } catch (Exception e) { + throw new JsonSerializationException(e); } + } - public static boolean isWrapper(String className) { - return WRAPPER_TYPES_CANONICAL.contains(className); + /** + * Convert an object to JSON using the chosen backend and write the result to + * the specified output stream. + * + * @param out + * The OutputStream that the resultant JSON will be written to. + * @param obj + * The object that will be converted to JSON. + * @throws IOException + * if writing to the output stream fails + * @throws JsonSerializationException + * if an exception occurs during serialization. + */ + static public void toJson(OutputStream out, Object obj) throws IOException { + String json; + try { + json = mapper.writeValueAsString(obj); + } catch (Exception jsonProcessingException) { + throw new JsonSerializationException(jsonProcessingException); } + if (json != null) + out.write(json.getBytes()); + } - /** - * Converts a snake_case String to a camelCase variant. - * - * @param s A String written in snake_case - * @return The same String but converted to camelCase - */ - static public String toCamelCase(String s) { - String[] parts = s.split("_"); - String camelCaseString = ""; - for (String part : parts) { - camelCaseString = camelCaseString + toCCase(part); - } - return String.format("%s%s", camelCaseString.substring(0, 1).toLowerCase(), camelCaseString.substring(1)); - } + // === method signatures begin === - /** - * Capitalizes the first character of the string while the rest is - * set to lower case. - * - * @param s The string - * @return A String that is all lower case except for the first character - */ - static public String toCCase(String s) { - return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + /** + * Convert the given object to JSON, as if the object were an instance of the + * given class. + * + * @param o + * The object to be serialized + * @param clazz + * The class to treat the object as + * @return The resultant JSON string + * @throws JsonSerializationException + * if an exception occurs during serialization + */ + public static String toJson(Object o, Class clazz) { + try { + return mapper.writerFor(clazz).writeValueAsString(o); + } catch (Exception e) { + throw new JsonSerializationException(e); } + } - /** - * Convert an Object to its JSON string form using the chosen - * JSON backend. - * - * @param o The object to be converted - * @return The object in String JSON form - * @throws JsonSerializationException if serialization fails - * @see #USING_GSON - */ - public static String toJson(Object o) { - try { - if (USING_GSON) { - return gson.toJson(o); - } - - return mapper.writeValueAsString(o); - } catch (Exception e) { - throw new JsonSerializationException(e); - } + /** + * Serialize the given object to JSON using the selected JSON backend and + * write the result to a file with the given filename. + * + * @param o + * The object to be serialized + * @param filename + * The name of the file to write the JSON to + * @throws IOException + * if writing to the file fails + * @throws JsonSerializationException + * if serialization throws an exception + */ + public static void toJsonFile(Object o, String filename) throws IOException { + byte[] json; + try { + json = mapper.writeValueAsBytes(o); + } catch (Exception e) { + throw new JsonSerializationException(e); } - /** - * Convert an object to JSON using the chosen backend - * and write the result to the specified output stream. - * - * @param out The OutputStream that the resultant JSON will be - * written to. - * @param obj The object that will be converted to JSON. - * @throws IOException if writing to the output stream fails - * @throws JsonSerializationException if an exception occurs during serialization. - * @see #USING_GSON - */ - static public void toJson(OutputStream out, Object obj) throws IOException { - String json; - try { - if (USING_GSON) { - json = gson.toJson(obj); - } else { - json = mapper.writeValueAsString(obj); - } - } catch (Exception jsonProcessingException) { - throw new JsonSerializationException(jsonProcessingException); - } - if (json != null) - out.write(json.getBytes()); + // try-wth-resources, ensures a file is closed even if an exception is + // thrown + try (FileOutputStream fos = new FileOutputStream(filename)) { + fos.write(json); } + } - // === method signatures begin === - - /** - * Convert the given object to JSON, as if the object were - * an instance of the given class. - * - * @param o The object to be serialized - * @param clazz The class to treat the object as - * @return The resultant JSON string - * @throws JsonSerializationException if an exception occurs during serialization - * @see #USING_GSON - */ - public static String toJson(Object o, Class clazz) { - try { - if (USING_GSON) { - return gson.toJson(o, clazz); - } - - return mapper.writerFor(clazz).writeValueAsString(o); - } catch (Exception e) { - throw new JsonSerializationException(e); - } - } + /** + * Converts a given String from camelCase to snake_case, setting the entire + * string to be lowercase. + * + * @param camelCase + * The camelCase string to be converted + * @return The string in snake_case form + */ + static public String toUnderScore(String camelCase) { + return toUnderScore(camelCase, false); + } - /** - * Serialize the given object to JSON using the selected - * JSON backend and write the result to a file with the - * given filename. - * - * @param o The object to be serialized - * @param filename The name of the file to write the JSON to - * @throws IOException if writing to the file fails - * @throws JsonSerializationException if serialization throws an exception - */ - public static void toJsonFile(Object o, String filename) throws IOException { - byte[] json; - try { - if (USING_GSON) { - json = gson.toJson(o).getBytes(); - } else { - json = mapper.writeValueAsBytes(o); - } - } catch (Exception e) { - throw new JsonSerializationException(e); + /** + * Converts a given String from camelCase to snake_case, If toLowerCase is + * true, the entire string will be set to lower case. If false, it will be set + * to uppercase, and if null the casing will not be changed. + * + * @param camelCase + * The camelCase string to be converted + * @param toLowerCase + * Whether the entire string should be lowercase, uppercase, or not + * changed (null) + * @return The string in snake_case form + */ + static public String toUnderScore(String camelCase, Boolean toLowerCase) { + + byte[] a = camelCase.getBytes(); + boolean lastLetterLower = false; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < a.length; ++i) { + boolean currentCaseUpper = Character.isUpperCase(a[i]); + + Character newChar = null; + if (toLowerCase != null) { + if (toLowerCase) { + newChar = (char) Character.toLowerCase(a[i]); + } else { + newChar = (char) Character.toUpperCase(a[i]); } + } else { + newChar = (char) a[i]; + } - //try-wth-resources, ensures a file is closed even if an exception is thrown - try (FileOutputStream fos = new FileOutputStream(filename)) { - fos.write(json); - } + sb.append(String.format("%s%c", (lastLetterLower && currentCaseUpper) ? "_" : "", newChar)); + lastLetterLower = !currentCaseUpper; } - /** - * Converts a given String from camelCase to snake_case, - * setting the entire string to be lowercase. - * - * @param camelCase The camelCase string to be converted - * @return The string in snake_case form - */ - static public String toUnderScore(String camelCase) { - return toUnderScore(camelCase, false); - } + return sb.toString(); - /** - * Converts a given String from camelCase to snake_case, - * If toLowerCase is true, the entire string will be set to - * lower case. If false, it will be set to uppercase, and if - * null the casing will not be changed. - * - * @param camelCase The camelCase string to be converted - * @param toLowerCase Whether the entire string should be lowercase, uppercase, - * or not changed (null) - * @return The string in snake_case form - */ - static public String toUnderScore(String camelCase, Boolean toLowerCase) { - - byte[] a = camelCase.getBytes(); - boolean lastLetterLower = false; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < a.length; ++i) { - boolean currentCaseUpper = Character.isUpperCase(a[i]); - - Character newChar = null; - if (toLowerCase != null) { - if (toLowerCase) { - newChar = (char) Character.toLowerCase(a[i]); - } else { - newChar = (char) Character.toUpperCase(a[i]); - } - } else { - newChar = (char) a[i]; - } - - sb.append(String.format("%s%c", (lastLetterLower && currentCaseUpper) ? "_" : "", newChar)); - lastLetterLower = !currentCaseUpper; - } + } - return sb.toString(); + /** + * Equivalent to {@link #isInteger(String)} + * + * @param string + * The String to be checked + * @return Whether the String can be parsed as an Integer + */ + @Deprecated + public static boolean tryParseInt(String string) { + try { + Integer.parseInt(string); + return true; + } catch (Exception e) { } + return false; + } - /** - * Equivalent to {@link #isInteger(String)} - * - * @param string The String to be checked - * @return Whether the String can be parsed as an Integer - */ - @Deprecated - public static boolean tryParseInt(String string) { - try { - Integer.parseInt(string); - return true; - } catch (Exception e) { - - } - return false; + public static String type(String type) { + int pos0 = type.indexOf("."); + if (pos0 > 0) { + return type; } + return String.format("org.myrobotlab.service.%s", type); + } - public static String type(String type) { - int pos0 = type.indexOf("."); - if (pos0 > 0) { - return type; - } - return String.format("org.myrobotlab.service.%s", type); + /** + * Get the simple name of a service type name. A simple name is the name of + * the service type without any package specifier. + * + * @param serviceType + * The service type in String form + * @return The simple name of the servide type + */ + public static String getSimpleName(String serviceType) { + int pos = serviceType.lastIndexOf("."); + if (pos > -1) { + return serviceType.substring(pos + 1); } + return serviceType; + } - /** - * Get the simple name of a service type name. - * A simple name is the name of the service type - * without any package specifier. - * - * @param serviceType The service type in String form - * @return The simple name of the servide type - */ - public static String getSimpleName(String serviceType) { - int pos = serviceType.lastIndexOf("."); - if (pos > -1) { - return serviceType.substring(pos + 1); - } - return serviceType; - } + public static String getSafeReferenceName(String name) { + return name.replaceAll("[@/ .-]", "_"); + } - public static String getSafeReferenceName(String name) { - return name.replaceAll("[@/ .-]", "_"); + /** + * Serializes the specified object to JSON, using + * {@link #mapper} with {@link #jacksonPrettyPrinter} to pretty-ify the + * result. + * + * @param ret + * The object to be serialized + * @return The object in pretty JSON form + */ + public static String toPrettyJson(Object ret) { + try { + return mapper.writer(jacksonPrettyPrinter).writeValueAsString(ret); + } catch (Exception e) { + throw new JsonSerializationException(e); } - /** - * Serializes the specified object to JSON, using - * {@link #prettyGson} or {@link #mapper} - * with {@link #jacksonPrettyPrinter} - * to pretty-ify the result. Which object is used depends on - * {@link #USING_GSON} - * - * @param ret The object to be serialized - * @return The object in pretty JSON form - */ - public static String toPrettyJson(Object ret) { - try { - if (USING_GSON) { - return prettyGson.toJson(ret); - } else { - - return mapper.writer(jacksonPrettyPrinter).writeValueAsString(ret); - } - } catch (Exception e) { - throw new JsonSerializationException(e); - } + } + /** + * Deserialize a given String into an array of Objects, treating the String as + * JSON and using the selected JSON backend. + * + * @param data + * A String containing a JSON array + * @return An array of Objects created by deserializing the JSON array + * @throws Exception + * If deserialization fails + */ + static public Object[] decodeArray(Object data) throws Exception { + // ITS GOT TO BE STRING - it just has to be !!! :) + String instr = (String) data; + // array of Strings ? - don't want to double encode ! + Object[] ret = null; + synchronized (data) { + ret = mapper.readValue(instr, Object[].class); } + return ret; + } - /** - * Deserialize a given String into an array of Objects, - * treating the String as JSON and using the selected JSON backend. - * - * @param data A String containing a JSON array - * @return An array of Objects created by deserializing the JSON array - * @throws Exception If deserialization fails - */ - static public Object[] decodeArray(Object data) throws Exception { - // ITS GOT TO BE STRING - it just has to be !!! :) - String instr = (String) data; - // array of Strings ? - don't want to double encode ! - Object[] ret = null; - synchronized (data) { - if (USING_GSON) { - ret = gson.fromJson(instr, Object[].class); - } else { - ret = mapper.readValue(instr, Object[].class); - } - } - return ret; - } + /** + * This is a Path to Message decoder - it takes a line of text and generates + * the appropriate msg with json encoded string parameters and either invokes + * (locally) or sendBlockingRemote (remotely) + * + *
    +   *
    +   * The expectation of this encoding is:
    +   *    if "/api/service/" is found - the end of that string is the starting point
    +   *    if "/api/service/" is not found - then the starting point of the string should be the service
    +   *      e.g "runtime/getUptime"
    +   *
    +   * Important to remember getRequestURI is NOT decoded and getPathInfo is.
    +   *
    +   *
    +   *
    +   * Method              URL-Decoded Result
    +   * ----------------------------------------------------
    +   * getContextPath()        no      /app
    +   * getLocalAddr()                  127.0.0.1
    +   * getLocalName()                  30thh.loc
    +   * getLocalPort()                  8480
    +   * getMethod()                     GET
    +   * getPathInfo()           yes     /a?+b
    +   * getProtocol()                   HTTP/1.1
    +   * getQueryString()        no      p+1=c+dp+2=e+f
    +   * getRequestedSessionId() no      S%3F+ID
    +   * getRequestURI()         no      /app/test%3F/a%3F+b;jsessionid=S+ID
    +   * getRequestURL()         no      http://30thh.loc:8480/app/test%3F/a%3F+b;jsessionid=S+ID
    +   * getScheme()                     http
    +   * getServerName()                 30thh.loc
    +   * getServerPort()                 8480
    +   * getServletPath()        yes     /test?
    +   * getParameterNames()     yes     [p 2, p 1]
    +   * getParameter("p 1")     yes     c d
    +   * 
    + * + * @param from + * - sender + * @param path + * - cli encoded msg + * @return - a Message derived from cli + */ + static public Message pathToMsg(String from, String path) { + // Message msg = Message.createMessage(from,"ls", null); + Message msg = new Message(); + msg.name = "runtime"; // default ? + msg.method = "ls"; + + // not required unless asynchronous + msg.sender = from; /** - * This is the Cli encoder - it takes a line of text and generates the - * appropriate msg from it to either invoke (locally) or sendBlockingRemote - * (remotely) - * *
    -     *
    -     * The expectation of this encoding is:
    -     *    if "/api/service/" is found - the end of that string is the starting point
    -     *    if "/api/service/" is not found - then the starting point of the string should be the service
    -     *      e.g "runtime/getUptime"
    -     *
    -     * Important to remember getRequestURI is NOT decoded and getPathInfo is.
    -     *
    +    
    +     The key to this interface is leading "/" ...
    +     "/" is absolute path - dir or execute
    +     without "/" means runtime method - spaces and quotes can be delimiters
    +    
    +     "/"  -  list services
    +     "/{serviceName}" - list data of service
    +     "/{serviceName}/" - list methods of service
    +     "/{serviceName}/{method}" - invoke method
    +     "/{serviceName}/{method}/" - list parameters of method
    +     "/{serviceName}/{method}/jsonP0/jsonP1/jsonP2/..." - invoke method with parameters
    +    
    +     or runtime
    +     {method}
    +     {method}/
    +     {method}/p01
          *
          *
    -     * Method              URL-Decoded Result
    -     * ----------------------------------------------------
    -     * getContextPath()        no      /app
    -     * getLocalAddr()                  127.0.0.1
    -     * getLocalName()                  30thh.loc
    -     * getLocalPort()                  8480
    -     * getMethod()                     GET
    -     * getPathInfo()           yes     /a?+b
    -     * getProtocol()                   HTTP/1.1
    -     * getQueryString()        no      p+1=c+dp+2=e+f
    -     * getRequestedSessionId() no      S%3F+ID
    -     * getRequestURI()         no      /app/test%3F/a%3F+b;jsessionid=S+ID
    -     * getRequestURL()         no      http://30thh.loc:8480/app/test%3F/a%3F+b;jsessionid=S+ID
    -     * getScheme()                     http
    -     * getServerName()                 30thh.loc
    -     * getServerPort()                 8480
    -     * getServletPath()        yes     /test?
    -     * getParameterNames()     yes     [p 2, p 1]
    -     * getParameter("p 1")     yes     c d
          * 
    - * - * @param contextPath - prefix to be added if supplied - * @param from - sender - * @param to - target service - * @param cmd - cli encoded msg - * @return - a Message derived from cli */ - static public Message cliToMsg(String contextPath, String from, String to, String cmd) { - Message msg = Message.createMessage(from, to, "ls", null); - - /** - *
    -
    -         The key to this interface is leading "/" ...
    -         "/" is absolute path - dir or execute
    -         without "/" means runtime method - spaces and quotes can be delimiters
    -
    -         "/"  -  list services
    -         "/{serviceName}" - list data of service
    -         "/{serviceName}/" - list methods of service
    -         "/{serviceName}/{method}" - invoke method
    -         "/{serviceName}/{method}/" - list parameters of method
    -         "/{serviceName}/{method}/p0/p1/p2" - invoke method with parameters
    -
    -         or runtime
    -         {method}
    -         {method}/
    -         {method}/p01
    -         *
    -         *
    -         * 
    - */ - - cmd = cmd.trim(); - - // remove uninteresting api prefix - if (cmd.startsWith(API_SERVICE_PATH)) { - cmd = cmd.substring(API_SERVICE_PATH.length()); - } - - if (contextPath != null) { - cmd = contextPath + cmd; - } - // assume runtime as 'default' - if (msg.name == null) { - // FIXME "runtime" really needs to be a constant at the very least - msg.name = "runtime"; - } + path = path.trim(); - // two possibilities - either it begins with "/" or it does not - // if it does begin with "/" its an absolute path to a dir, ls, or invoke - // if not then its a runtime method - - if (cmd.startsWith("/")) { - // ABSOLUTE PATH !!! - String[] parts = cmd.split("/"); - - if (parts.length < 3) { - msg.method = "ls"; - msg.data = new Object[]{cmd}; - return msg; - } - - // fix me diff from 2 & 3 "/" - if (parts.length >= 3) { - // prepare to parse the arguments - - msg.name = parts[1]; - // prepare the method - msg.method = parts[2].trim(); - - // FIXME - to encode or not to encode that is the question ... - // This source comes from the cli - which is "all" strings - // in theory it needs to be decoded from an all strings interface - // json is an all string interface so we will decode from cli strings - // (not json) - // using a json decoder - cuz it will work :P - and string will decode - // to a string - Object[] payload = new Object[parts.length - 3]; - for (int i = 3; i < parts.length; ++i) { - if (isInteger(parts[i])) { - payload[i - 3] = makeInteger(parts[i]); - } else if (isDouble(parts[i])) { - payload[i - 3] = makeDouble(parts[i]); - } else if (parts[i].equals("true") || parts[i].equals("false")) { - payload[i - 3] = makeBoolean(parts[i]); - } else { // String - // sloppy as the cli does not require quotes \" but json does - // humans won't add quotes - but we will - payload[i - 3] = parts[i]; - } - } - - msg.data = payload; - } - return msg; - } else { - // NOT ABOSLUTE PATH - SIMILAR TO EXECUTING IN THE RUNTIME /usr/bin path - // (ie runtime methods!) - // spaces for parameter delimiters ? - String[] spaces = cmd.split(" "); - // FIXME - need to deal with double quotes e.g. func A "B and C" D - p0 = - // "A" p1 = "B and C" p3 = "D" - msg.method = spaces[0]; - Object[] payload = new Object[spaces.length - 1]; - for (int i = 1; i < spaces.length; ++i) { - // webgui will never use this section of code - // currently the codepath is only excercised by InProcessCli - // all of this methods will be "optimized" single commands to runtime (i - // think) - // so we are going to error on the side of String parameters - other - // data types will have problems - payload[i - 1] = spaces[i]; - } - msg.data = payload; - - return msg; - } + // remove uninteresting api prefix + if (path.startsWith(API_SERVICE_PATH)) { + path = path.substring(API_SERVICE_PATH.length()); } - /** - * Parse the specified data as an Integer. If parsing - * fails, returns null - * - * @param data The String to be coerced into an Integer - * @return the data as an Integer, if parsing fails then null instead - */ - static public Integer makeInteger(String data) { - try { - return Integer.parseInt(data); - } catch (Exception e) { - } - return null; - } + // two possibilities - either it begins with "/" or it does not + // if it does begin with "/" its an absolute path to a dir, ls, or invoke + // if not then its a runtime method + + if (path.startsWith("/")) { + // ABSOLUTE PATH !!! + String[] parts = path.split("/"); // <- this breaks things ! e.g. + // /runtime/connect/"http://localhost:8888" + // path parts less than 3 is a dir or ls + if (parts.length < 3) { + // this morphs a path which has less than 3 parts + // into a runtime "ls" method call to do reflection of services or service methods + // e.g. /clock -> /runtime/ls/"/clock" + // e.g. /clock/ -> /runtime/ls/"/clock/" + + msg.method = "ls"; + msg.data = new Object[] { "\"" + path + "\""}; + return msg; + } - /** - * Checks whether the given String can be parsed as - * an Integer - * - * @param data The string to be checked - * @return true if the data can be parsed as an Integer, false otherwise - */ - static public boolean isInteger(String data) { - try { - Integer.parseInt(data); - return true; - } catch (Exception e) { - } - return false; + // ["", "runtime", "shutdown"] + if (parts.length == 3) { + msg.name = parts[1]; + msg.method = parts[2]; + return msg; + } + + // fix me diff from 2 & 3 "/" + if (parts.length >= 3) { + // prepare to parse the arguments + + msg.name = parts[1]; + // prepare the method + msg.method = parts[2].trim(); + + // remove the first 3 slashes + String data = path.substring(("/" + msg.name + "/" + msg.method + "/").length()); + msg.data = extractJsonParamsFromPath(data); + } + return msg; + } else { + // e.g. ls /webgui/ + // retrieves all webgui methods + String[] spaces = path.split(" "); + // FIXME - need to deal with double quotes e.g. func A "B and C" D - p0 = + // "A" p1 = "B and C" p3 = "D" + msg.method = spaces[0]; + Object[] payload = new Object[spaces.length - 1]; + for (int i = 1; i < spaces.length; ++i) { + // webgui will never use this section of code + // currently the codepath is only excercised by InProcessCli + // all of this methods will be "optimized" single commands to runtime (i + // think) + // so we are going to error on the side of String parameters - other + // data types will have problems + payload[i - 1] = spaces[i]; + } + msg.data = payload; + + return msg; } + } - /** - * Checks whether the given String can be parsed as - * a Double - * - * @param data The string to be checked - * @return true if the data can be parsed as a Double, false otherwise - */ - static public boolean isDouble(String data) { - try { - Double.parseDouble(data); - return true; - } catch (Exception e) { + /** + * extractJsonFromPath exects a forwad slash deliminated string + * + *
    +   * json1 / json2 / json3
    +   * 
    + * + * It will return the json parts in a string array + * + * @param input + * @return + */ + public static Object[] extractJsonParamsFromPath(String input) { + List fromJson = new ArrayList<>(); + StringBuilder currentJson = new StringBuilder(); + boolean insideQuotes = false; + + for (char c : input.toCharArray()) { + if (c == '"' && !insideQuotes) { + insideQuotes = true; + } else if (c == '"' && insideQuotes) { + insideQuotes = false; + } + + if (c == '/' && !insideQuotes) { + if (currentJson.length() > 0) { + fromJson.add(currentJson.toString()); + currentJson = new StringBuilder(); } - return false; + } else { + currentJson.append(c); + } } - /** - * Parse the specified data as a Double. If parsing - * fails, returns null - * - * @param data The String to be coerced into a Doubled= - * @return the data as a Double, if parsing fails then null instead - */ - static public Double makeDouble(String data) { - try { - return Double.parseDouble(data); - } catch (Exception e) { - } - return null; + if (currentJson.length() > 0) { + fromJson.add(currentJson.toString()); } - /** - * Checks whether the given String can be parsed as - * a Boolean - * - * @param data The string to be checked - * @return true if the data can be parsed as a boolean, false otherwise - */ - @SuppressWarnings("ResultOfMethodCallIgnored") - static public Boolean isBoolean(String data) { - try { - Boolean.parseBoolean(data); - // The above will return false and not throw - // exception for most cases - return "true".equals(data) || "false".equals(data); - } catch (Exception ignored) { - return false; - } + return fromJson.toArray(new Object[0]); + } + + /** + * Parse the specified data as an Integer. If parsing fails, returns null + * + * @param data + * The String to be coerced into an Integer + * @return the data as an Integer, if parsing fails then null instead + */ + static public Integer makeInteger(String data) { + try { + return Integer.parseInt(data); + } catch (Exception e) { } + return null; + } - /** - * Parse the specified data as a Boolean. If parsing - * fails, returns null - * - * @param data The String to be coerced into a Boolean - * @return the data as a boolean, if parsing fails then null instead - */ - static public Boolean makeBoolean(String data) { - try { - return Boolean.parseBoolean(data); - } catch (Exception e) { - } - return null; + /** + * Checks whether the given String can be parsed as an Integer + * + * @param data + * The string to be checked + * @return true if the data can be parsed as an Integer, false otherwise + */ + static public boolean isInteger(String data) { + try { + Integer.parseInt(data); + return true; + } catch (Exception e) { } + return false; + } - /** - * Get a description for each of the supported APIs - * - * @return A list containing a description for each supported API - */ - static public List getApis() { - List ret = new ArrayList<>(); - ret.add(new ApiDescription("message", "{scheme}://{host}:{port}" + API_MESSAGES_PATH, "ws://localhost:8888" + API_MESSAGES_PATH, - "An asynchronous api useful for bi-directional websocket communication, primary messages api for the webgui. URI is " + API_MESSAGES_PATH + " data contains a json encoded Message structure")); - ret.add(new ApiDescription("service", "{scheme}://{host}:{port}" + API_SERVICE_PATH, "http://localhost:8888" + API_SERVICE_PATH +"/runtime/getUptime", - "An synchronous api useful for simple REST responses")); - return ret; + /** + * Checks whether the given String can be parsed as a Double + * + * @param data + * The string to be checked + * @return true if the data can be parsed as a Double, false otherwise + */ + static public boolean isDouble(String data) { + try { + Double.parseDouble(data); + return true; + } catch (Exception e) { } + return false; + } - /** - * Creates a properly double encoded Json msg string. Why double encode ? - - * because initial decode should decode router and header information. The - * first decode will leave the payload a array of json strings. The header - * will send it to a another process or it will go to the MethodCache of some - * service. The MethodCache will decode a 2nd time based on a method signature - * key match (key based on parameter types). - * - * @param sender the sender of the message - * @param sendingMethod the method sending it - * @param name dest service - * @param method dest method - * @param params params to pass - * @return the string representation of the json message - */ - public static String createJsonMsg(String sender, String sendingMethod, String name, String method, Object... params) { - Message msg = Message.createMessage(sender, name, method, null); - msg.sendingMethod = sendingMethod; - Object[] d = null; - if (params != null) { - d = new Object[params.length]; - for (int i = 0; i < params.length; ++i) { - d[i] = CodecUtils.toJson(params[i]); - } - msg.setData(d); - } - return CodecUtils.toJson(msg); + /** + * Parse the specified data as a Double. If parsing fails, returns null + * + * @param data + * The String to be coerced into a Doubled= + * @return the data as a Double, if parsing fails then null instead + */ + static public Double makeDouble(String data) { + try { + return Double.parseDouble(data); + } catch (Exception e) { } + return null; + } - /** - * Encodes a Message as JSON, double-encoding the - * {@link Message#data} if not already double-encoded. - * The selected JSON backend will be used. - * - * @param inMsg The message to be encoded - * @return A String representation of the message and all of its - * members in JSON format. - * @see #USING_GSON - */ - public static String toJsonMsg(Message inMsg) { - if ("json".equals(inMsg.encoding)) { - // msg already has json encoded data parameters - // just encode the msg envelope - return CodecUtils.toJson(inMsg); - } - Message msg = new Message(inMsg); - msg.encoding = "json"; - Object[] params = inMsg.getData(); - Object[] d = null; - if (params != null) { - d = new Object[params.length]; - for (int i = 0; i < params.length; ++i) { - d[i] = CodecUtils.toJson(params[i]); - } - msg.setData(d); - } - return CodecUtils.toJson(msg); + /** + * Checks whether the given String can be parsed as a Boolean + * + * @param data + * The string to be checked + * @return true if the data can be parsed as a boolean, false otherwise + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + static public Boolean isBoolean(String data) { + try { + Boolean.parseBoolean(data); + // The above will return false and not throw + // exception for most cases + return "true".equals(data) || "false".equals(data); + } catch (Exception ignored) { + return false; } + } - @Deprecated - public static Message toJsonParameters(Message msg) { - Object[] data = msg.getData(); - if (data != null) { - Object[] params = new Object[data.length]; - for (int i = 0; i < params.length; ++i) { - params[i] = toJson(data[i]); - } - msg.setData(params); - } - return msg; + /** + * Parse the specified data as a Boolean. If parsing fails, returns null + * + * @param data + * The String to be coerced into a Boolean + * @return the data as a boolean, if parsing fails then null instead + */ + static public Boolean makeBoolean(String data) { + try { + return Boolean.parseBoolean(data); + } catch (Exception e) { } + return null; + } - /** - * Serialize the given object to YAML. - * - * @param o The object to be serialized - * @return A String formatted as YAML representing the object - */ - public static String toYaml(Object o) { - // not thread safe - so we new here - DumperOptions options = new DumperOptions(); - options.setIndent(2); - options.setPrettyFlow(true); - // options.setBeanAccess(BeanAccess.FIELD); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - /** - *
    -         *  How to suppress null fields if desired
    -         Representer representer = new Representer() {
    -         @Override
    -         protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
    -         // if value of property is null, ignore it.
    -         if (propertyValue == null) {
    -         return null;
    -         } else {
    -         return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
    -         }
    -         }
    -         };
    -         * 
    - */ - - Yaml yaml = new Yaml(options); - // yaml.setBeanAccess(BeanAccess.FIELD); - String c = yaml.dump(o); - return c; + /** + * Get a description for each of the supported APIs + * + * @return A list containing a description for each supported API + */ + static public List getApis() { + List ret = new ArrayList<>(); + ret.add(new ApiDescription("message", "{scheme}://{host}:{port}" + API_MESSAGES_PATH, "ws://localhost:8888" + API_MESSAGES_PATH, + "An asynchronous api useful for bi-directional websocket communication, primary messages api for the webgui. URI is " + API_MESSAGES_PATH + + " data contains a json encoded Message structure")); + ret.add(new ApiDescription("service", "{scheme}://{host}:{port}" + API_SERVICE_PATH, "http://localhost:8888" + API_SERVICE_PATH + "/runtime/getUptime", + "An synchronous api useful for simple REST responses")); + return ret; + } + + /** + * Creates a properly double encoded Json msg string. Why double encode ? - + * because initial decode should decode router and header information. The + * first decode will leave the payload a array of json strings. The header + * will send it to a another process or it will go to the MethodCache of some + * service. The MethodCache will decode a 2nd time based on a method signature + * key match (key based on parameter types). + * + * @param sender + * the sender of the message + * @param sendingMethod + * the method sending it + * @param name + * dest service + * @param method + * dest method + * @param params + * params to pass + * @return the string representation of the json message + */ + public static String createJsonMsg(String sender, String sendingMethod, String name, String method, Object... params) { + Message msg = Message.createMessage(sender, name, method, null); + msg.sendingMethod = sendingMethod; + Object[] d = null; + if (params != null) { + d = new Object[params.length]; + for (int i = 0; i < params.length; ++i) { + d[i] = CodecUtils.toJson(params[i]); + } + msg.setData(d); } + return CodecUtils.toJson(msg); + } - public static String allToYaml(Iterator o) { - // not thread safe - so we new here - DumperOptions options = new DumperOptions(); - options.setIndent(2); - options.setPrettyFlow(true); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - - Yaml yaml = new Yaml(options); - // yaml.setBeanAccess(BeanAccess.FIELD); - String c = yaml.dumpAll(o); - return c; + /** + * Encodes a Message as JSON, double-encoding the {@link Message#data} if not + * already double-encoded. The selected JSON backend will be used. + * + * @param inMsg + * The message to be encoded + * @return A String representation of the message and all of its members in + * JSON format. + */ + public static String toJsonMsg(Message inMsg) { + if ("json".equals(inMsg.encoding)) { + // msg already has json encoded data parameters + // just encode the msg envelope + return CodecUtils.toJson(inMsg); } + Message msg = new Message(inMsg); + msg.encoding = "json"; + Object[] params = inMsg.getData(); + Object[] d = null; + if (params != null) { + d = new Object[params.length]; + for (int i = 0; i < params.length; ++i) { + d[i] = CodecUtils.toJson(params[i]); + } + msg.setData(d); + } + return CodecUtils.toJson(msg); + } - public static Iterable allFromYaml(InputStream is) { - // Yaml yaml = new Yaml(new Constructor(clazz)); - Yaml yaml = new Yaml(); - // yaml.setBeanAccess(BeanAccess.FIELD); - return yaml.loadAll(is); + @Deprecated + public static Message toJsonParameters(Message msg) { + Object[] data = msg.getData(); + if (data != null) { + Object[] params = new Object[data.length]; + for (int i = 0; i < params.length; ++i) { + params[i] = toJson(data[i]); + } + msg.setData(params); } + return msg; + } + /** + * Serialize the given object to YAML. + * + * @param o + * The object to be serialized + * @return A String formatted as YAML representing the object + */ + public static String toYaml(Object o) { + // not thread safe - so we new here + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setPrettyFlow(true); + // options.setBeanAccess(BeanAccess.FIELD); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); /** - * Deserialize the given string into the specified class, - * treating the string as YAML. - * - * @param data The YAML to be deserialized - * @param clazz The target class - * @param The type of the target class - * @return An instance of the target class with the state given by the YAML string + *
    +     *  How to suppress null fields if desired
    +     Representer representer = new Representer() {
    +     @Override
    +     protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
    +     // if value of property is null, ignore it.
    +     if (propertyValue == null) {
    +     return null;
    +     } else {
    +     return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
    +     }
    +     }
    +     };
    +     * 
    */ - public static T fromYaml(String data, Class clazz) { - Yaml yaml = new Yaml(new Constructor(clazz)); - // yaml.setBeanAccess(BeanAccess.FIELD); - return (T) yaml.load(data); - } + Yaml yaml = new Yaml(options); + // yaml.setBeanAccess(BeanAccess.FIELD); + String c = yaml.dump(o); + return c; + } - /** - * Checks if the service name is local to the current process instance - * @param name The service name to be checked - * @return Whether the service name is local to the given ID - */ - public static boolean isLocal(String name) { - if (!name.contains("@")) { - return true; - } - return name.substring(name.indexOf("@") + 1).equals(Platform.getLocalInstance().getId()); + public static String allToYaml(Iterator o) { + // not thread safe - so we new here + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setPrettyFlow(true); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + Yaml yaml = new Yaml(options); + // yaml.setBeanAccess(BeanAccess.FIELD); + String c = yaml.dumpAll(o); + return c; } - - - /** - * Checks if the service name given by name is local, - * i.e. it has no remote ID (has no '@' symbol), or - * if it has a remote ID it matches the ID given. - * - * @param name The service name to be checked - * @param id The runtime ID of the local instance - * @return Whether the service name is local to the given ID - */ - public static boolean isLocal(String name, String id) { - if (!name.contains("@")) { - return true; - } - return name.substring(name.indexOf("@") + 1).equals(id); + + public static Iterable allFromYaml(InputStream is) { + // Yaml yaml = new Yaml(new Constructor(clazz)); + Yaml yaml = new Yaml(); + // yaml.setBeanAccess(BeanAccess.FIELD); + return yaml.loadAll(is); + } + + /** + * Deserialize the given string into the specified class, treating the string + * as YAML. + * + * @param data + * The YAML to be deserialized + * @param clazz + * The target class + * @param + * The type of the target class + * @return An instance of the target class with the state given by the YAML + * string + */ + public static T fromYaml(String data, Class clazz) { + Yaml yaml = new Yaml(new Constructor(clazz)); + // yaml.setBeanAccess(BeanAccess.FIELD); + return (T) yaml.load(data); + } + + /** + * Checks if the service name is local to the current process instance + * + * @param name + * The service name to be checked + * @return Whether the service name is local to the given ID + */ + public static boolean isLocal(String name) { + if (!name.contains("@")) { + return true; } + return name.substring(name.indexOf("@") + 1).equals(Platform.getLocalInstance().getId()); + } - /** - * Read a YAML file given by the filename and convert it into - * a ServiceConfig object by deserialization. - * - * @param filename The name of the YAML file - * @return The equivalent ServiceConfig object - * @throws IOException if reading the file fails - */ - public static ServiceConfig readServiceConfig(String filename) throws IOException { - String data = Files.readString(Paths.get(filename)); - Yaml yaml = new Yaml(); - return yaml.load(data); + /** + * Checks if the service name given by name is local, i.e. it has no remote ID + * (has no '@' symbol), or if it has a remote ID it matches the ID given. + * + * @param name + * The service name to be checked + * @param id + * The runtime ID of the local instance + * @return Whether the service name is local to the given ID + */ + public static boolean isLocal(String name, String id) { + if (!name.contains("@")) { + return true; } + return name.substring(name.indexOf("@") + 1).equals(id); + } - /** - * Set a field of the given object identified by the - * given field name to the given value. If the field - * does not exist or the value is of the wrong type, - * this method is a no-op. - * - * @param o The object whose field will be modified - * @param field The name of the field to be modified - * @param value The new value to set the field to - */ - public static void setField(Object o, String field, Object value) { - try { - // TODO - handle all types :P - Field f = o.getClass().getDeclaredField(field); - f.setAccessible(true); - f.set(o, value); - } catch (Exception e) { - /** don't care - if its not there don't set it */ - } + /** + * Read a YAML file given by the filename and convert it into a ServiceConfig + * object by deserialization. + * + * @param filename + * The name of the YAML file + * @return The equivalent ServiceConfig object + * @throws IOException + * if reading the file fails + */ + public static ServiceConfig readServiceConfig(String filename) throws IOException { + String data = Files.readString(Paths.get(filename)); + Yaml yaml = new Yaml(); + return yaml.load(data); + } + + /** + * Set a field of the given object identified by the given field name to the + * given value. If the field does not exist or the value is of the wrong type, + * this method is a no-op. + * + * @param o + * The object whose field will be modified + * @param field + * The name of the field to be modified + * @param value + * The new value to set the field to + */ + public static void setField(Object o, String field, Object value) { + try { + // TODO - handle all types :P + Field f = o.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(o, value); + } catch (Exception e) { + /** don't care - if its not there don't set it */ } + } - public static void main(String[] args) { - LoggingFactory.init(Level.INFO); + public static void main(String[] args) { + LoggingFactory.init(Level.INFO); - try { - - Object o = readServiceConfig("data/config/InMoov2_FingerStarter/i01.chatBot.yml"); + try { - String json = CodecUtils.fromJson("test", String.class); - log.info("json {}", json); - json = CodecUtils.fromJson("a test", String.class); - log.info("json {}", json); - json = CodecUtils.fromJson("\"a/test\"", String.class); - log.info("json {}", json); - CodecUtils.fromJson("a/test", String.class); + Object o = readServiceConfig("data/config/InMoov2_FingerStarter/i01.chatBot.yml"); - } catch (Exception e) { - log.error("main threw", e); - } + String json = CodecUtils.fromJson("test", String.class); + log.info("json {}", json); + json = CodecUtils.fromJson("a test", String.class); + log.info("json {}", json); + json = CodecUtils.fromJson("\"a/test\"", String.class); + log.info("json {}", json); + CodecUtils.fromJson("a/test", String.class); + + } catch (Exception e) { + log.error("main threw", e); } + } /** * A description of an API type @@ -1617,8 +1601,8 @@ public int hashCode() { } /** - * Single parameter from JSON. Will use default return type, currently LinkedTreeMap - * to return a POJO object that can be easily accessed. + * Single parameter from JSON. Will use default return type, currently + * LinkedTreeMap to return a POJO object that can be easily accessed. * * @param json * @return @@ -1630,19 +1614,19 @@ public static Object fromJson(String json) { public static String toBase64(byte[] bytes) { return Base64.getEncoder().encodeToString(bytes); } - + public static byte[] fromBase64(String input) { return Base64.getDecoder().decode(input); } - + public static String removeEnd(final String str, final String remove) { if (str == null || str.length() == 0 || remove == null || remove.length() == 0) { - return str; + return str; } if (str.endsWith(remove)) { - return str.substring(0, str.length() - remove.length()); + return str.substring(0, str.length() - remove.length()); } return str; -} + } } diff --git a/src/main/java/org/myrobotlab/codec/MethodCache.java b/src/main/java/org/myrobotlab/codec/MethodCache.java deleted file mode 100644 index 7f2760010b..0000000000 --- a/src/main/java/org/myrobotlab/codec/MethodCache.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.myrobotlab.codec; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.TreeMap; - -@Deprecated /* use new framework MethodCache */ -public class MethodCache { - - static final private HashMap cache = new HashMap(); - - static final public String getSignature(Class clazz, String methodName, int ordinal) { - return String.format("%s/%s-%d", clazz.getSimpleName(), methodName, ordinal); - } - - static final public Class[] getCandidateOnOrdinalSignature(Class clazz, String methodName, int ordinal) throws NoSuchMethodException { - String signature = getSignature(clazz, methodName, ordinal); - if (cache.containsKey(signature)) { - Method m = cache.get(signature); - return m.getParameterTypes(); - } else { - TreeMap methodScore = new TreeMap(); - // changed to getMethods to support inheritance - // if failure - overloading funny re-implementing a vTable in c++ - // Method[] methods = clazz.getDeclaredMethods(); - Method[] methods = clazz.getMethods(); - for (Method method : methods) { - - // FIXME - future Many to one Map - if incoming data can "hint" would be - // an optimization - if (methodName.equals(method.getName())) { - // name matches - lets do more checking - Class[] pTypes = method.getParameterTypes(); - int score = 0; - if (ordinal == pTypes.length) { - // param length matches - boolean interfaceInParamList = false; - for (int i = 0; i < pTypes.length; ++i) { - // we don't support interfaces - // because what will we decode too ? - // we just can't ! :) - Class type = pTypes[i]; - - /* - * BAD ASSUMPTION - SOME CODECs have a default class they - * serialize from for List Map HashSet etc.. - */ - /* - * if (type.isInterface()) { interfaceInParamList = true; break; } - */ - - if (type.isPrimitive() || type.equals(String.class)) { - ++score; - } - - } - - if (!interfaceInParamList) { - // rank / score method - methodScore.put(score, method); - } - } - } - } // we checked all methods - - if (methodScore.size() > 0) { - return methodScore.get(methodScore.lastKey()).getParameterTypes(); - } else { - throw new NoSuchMethodException(String.format("could not find %s.%s(ordinal %d) in declared methods", clazz.getSimpleName(), methodName, ordinal)); - } - } - - } - - final public static void cache(Class clazz, Method method) { - cache.put(getSignature(clazz, method.getName(), method.getParameterTypes().length), method); - } - -} diff --git a/src/main/java/org/myrobotlab/codec/PolymorphicSerializer.java b/src/main/java/org/myrobotlab/codec/PolymorphicSerializer.java deleted file mode 100644 index a4d8342b52..0000000000 --- a/src/main/java/org/myrobotlab/codec/PolymorphicSerializer.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.myrobotlab.codec; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.module.noctordeser.NoCtorDeserModule; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import org.myrobotlab.codec.json.GsonPolymorphicTypeAdapterFactory; -import org.myrobotlab.codec.json.JacksonPolymorphicModule; -import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.Registration; - -import java.io.Serializable; - - -public class PolymorphicSerializer { - - public static void main(String[] args) throws JsonProcessingException { - //GSON support still works but is kinda wonky - Gson gson = new GsonBuilder().registerTypeAdapterFactory(new GsonPolymorphicTypeAdapterFactory()).create(); - String msgString = gson.toJson( - Message.createMessage("runtime", "runtime", "getId", - new Registration("obsidian", "runtime", "Runtime") - ) - ); - //Have to use `Serializable.class` because `Object.class` uses the default treemap deserializer, no way to override - //So it technically works, but is a little odd to use - Message msgFromString = (Message) gson.fromJson(msgString, Serializable.class); - //Notice that the message data is not preserved, because it is an Object type in Message - //and GSON does not dispatch to custom de/serializers when the type is Object. - System.out.println(msgFromString); - - //Jackson setup - ObjectMapper mapper = new ObjectMapper(); - - //This allows Jackson to work just like GSON when no default constructor is available - mapper.registerModule(new NoCtorDeserModule()); - - //Actually add our polymorphic support - mapper.registerModule(JacksonPolymorphicModule.getPolymorphicModule()); - - //Disables Jackson's automatic property detection - mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - mapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.ANY); - - //All ready to use now - - msgString = mapper.writeValueAsString( - Message.createMessage("runtime", "runtime", "getId", - new Registration("obsidian", "runtime", "Runtime") - ) - ); - - //Can use Object.class just fine, so generic objects are deserialized correctly - //since type erasure makes everything look like an Object - //Notably, Message works just fine even though it stores an Object array - Message msg = (Message) mapper.readValue(msgString, Object.class); - System.out.println(msg); - - System.out.println(new ObjectMapper().readValue("{\"help\": 10}", Object.class).getClass()); - } -} diff --git a/src/main/java/org/myrobotlab/codec/json/GsonPolymorphicTypeAdapterFactory.java b/src/main/java/org/myrobotlab/codec/json/GsonPolymorphicTypeAdapterFactory.java deleted file mode 100644 index efac1fe8ba..0000000000 --- a/src/main/java/org/myrobotlab/codec/json/GsonPolymorphicTypeAdapterFactory.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.myrobotlab.codec.json; - -import com.google.gson.*; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import org.myrobotlab.codec.CodecUtils; - -import java.io.IOException; -import java.util.Collection; -import java.util.Map; - -/** - * A {@link TypeAdapterFactory} that enables polymorphic operation. - * The serialization adapter adds a field with the name taken from - * {@link CodecUtils#CLASS_META_KEY} and a value equal - * to the object's fully qualified class name. - * - * The deserialization adapter checks if the JSON has - * {@link CodecUtils#CLASS_META_KEY}, and if so it will use - * the value as the target type. - * - * @author AutonomicPerfectionist - */ -public class GsonPolymorphicTypeAdapterFactory implements TypeAdapterFactory { - - /** - * The TypeAdapter used to create JsonElements - */ - protected TypeAdapter elementAdapter; - protected TypeAdapterFactory taf; - - @SuppressWarnings("unchecked") - public TypeAdapter create(Gson gson, TypeToken type) { - if(!Object.class.isAssignableFrom(type.getRawType()) || String.class.isAssignableFrom(type.getRawType()) - || CodecUtils.WRAPPER_TYPES.contains(type.getRawType()) || Object[].class.isAssignableFrom(type.getRawType()) - || Collection.class.isAssignableFrom(type.getRawType()) || Map.class.isAssignableFrom(type.getRawType())) - return null; - this.taf=this; - TypeAdapter delegate = (TypeAdapter) gson.getDelegateAdapter(this, type); - elementAdapter = gson.getAdapter(JsonElement.class); - TypeAdapter result = (TypeAdapter) new PolymorphicTypeAdapter(type, delegate, gson); - return result.nullSafe(); - - } - - /** - * A type adapter to perform polymorphic deserialization - * and serialization operations. Should only be created with - * {@link GsonPolymorphicTypeAdapterFactory#create(Gson, TypeToken)}. - * - * @author AutonomicPerfectionist - */ - protected class PolymorphicTypeAdapter extends TypeAdapter { - - protected Gson gson; - protected TypeToken type; - protected TypeAdapter delegate; - - public PolymorphicTypeAdapter(TypeToken type, TypeAdapter delegate, Gson gson) { - this.type = type; - this.delegate = delegate; - this.gson = gson; - } - - @Override - public void write(JsonWriter out, Object value) throws IOException { - if(value != null) { - try { - JsonElement element = delegate.toJsonTree(value); - if(element.isJsonObject()) { - JsonObject object = delegate.toJsonTree(value).getAsJsonObject(); - object.addProperty(CodecUtils.CLASS_META_KEY, value.getClass().getName()); - elementAdapter.write(out, object); - } else - delegate.write(out, value); - - } catch (IllegalArgumentException iae) { - delegate.write(out, value); - } - } - else { - delegate.write(out, null); - } - } - - @Override - public Object read(JsonReader in) throws IOException { - JsonElement element = elementAdapter.read(in); - if (element.isJsonObject()) { - JsonObject object = element.getAsJsonObject(); - if (object.has(CodecUtils.CLASS_META_KEY)) { - String className=object.get(CodecUtils.CLASS_META_KEY).getAsString(); - try { - Class clz = Class.forName(className); - if(type.getRawType().isAssignableFrom(clz)) { - TypeAdapter adapter = gson.getDelegateAdapter(taf, TypeToken.get(clz)); - return adapter.fromJsonTree(element); - } - } - catch (Exception ignored) { - } - } - } - - //If element is not a json object, doesn't have the key, - //an exception occurs, or requested class is not a superclass - //of embedded class, will fallthrough to here - return delegate.fromJsonTree(element); - } - } -} diff --git a/src/main/java/org/myrobotlab/framework/MethodCache.java b/src/main/java/org/myrobotlab/framework/MethodCache.java index f27786fa6f..9732031ff0 100644 --- a/src/main/java/org/myrobotlab/framework/MethodCache.java +++ b/src/main/java/org/myrobotlab/framework/MethodCache.java @@ -2,6 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.codec.json.JsonDeserializationException; import org.myrobotlab.logging.LoggerFactory; import org.slf4j.Logger; @@ -502,6 +503,7 @@ private String getMethodOrdinalKey(String fullType, String methodName, int param public List getOrdinalMethods(Class object, String methodName, int parameterSize) { if (object == null) { log.error("getOrdinalMethods on a null object "); + return null; } String objectKey = object.getTypeName(); @@ -575,20 +577,17 @@ public List getRemoteOrdinalMethods(Class object, String methodN Class[] paramTypes = methodEntry.getParameterTypes(); try { for (int i = 0; i < encodedParams.length; ++i) { - if (CodecUtils.JSON_DEFAULT_OBJECT_TYPE.equals(encodedParams[i].getClass())) { - // specific gson implementation - // rather than double encode everything - i have chosen - // to re-encode objects back to string since gson will decode them - // all ot linked tree maps - if the json decoder changes from gson - // this will probably need to change too - encodedParams[i] = CodecUtils.toJson(encodedParams[i]); - } - params[i] = CodecUtils.fromJson((String) encodedParams[i], paramTypes[i]); +// try { + params[i] = CodecUtils.fromJson((String) encodedParams[i], paramTypes[i]); +// } catch(JsonDeserializationException e) { +// log.info("could not decode threw {}.{}( ordinal[{}] {} {})- assuming String - missing quotes?", clazz.getSimpleName(), methodName, i, paramTypes[i].getSimpleName(), encodedParams[i]); +// // load raw string on +// params[i] = encodedParams[i]; +// } } // successfully decoded params return params; } catch (Exception e) { - log.info("getDecodedParameters threw clazz {} method {} params {} Message: {}", clazz, methodName, encodedParams.length, e.getMessage()); } } diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 3bee512f48..f0c83daa16 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -188,7 +188,7 @@ public abstract class Service implements Runnable, Serializable, ServiceInterfac /** * This is the map of interfaces - its really "static" information, since its - * a definition. However, since gson will not process statics - we are making + * a definition. However, since serialization will not process statics - we are making * it a member variable */ protected Map interfaceSet; diff --git a/src/main/java/org/myrobotlab/framework/Status.java b/src/main/java/org/myrobotlab/framework/Status.java index bfd1a0c83c..caba7dfad7 100644 --- a/src/main/java/org/myrobotlab/framework/Status.java +++ b/src/main/java/org/myrobotlab/framework/Status.java @@ -21,7 +21,7 @@ /** * Goal is to have a very simple Pojo with only a few (native Java helper * methods) WARNING !!! - this class used to extend Exception or Throwable - but - * the gson serializer would stack overflow with self reference issue + * the serializer would stack overflow with self reference issue * * TODO - allow radix tree searches for "keys" ??? * diff --git a/src/main/java/org/myrobotlab/framework/TypeConverter.java b/src/main/java/org/myrobotlab/framework/TypeConverter.java index c6dad571dd..051ae50e34 100644 --- a/src/main/java/org/myrobotlab/framework/TypeConverter.java +++ b/src/main/java/org/myrobotlab/framework/TypeConverter.java @@ -22,8 +22,6 @@ public class TypeConverter { public final static Logger log = LoggerFactory.getLogger(TypeConverter.class); - // private static Gson gson = new Gson(); - // Possible Optimization -> pointers to known method signatures - // optimization so that once a // method's signature is processed and diff --git a/src/main/java/org/myrobotlab/i2c/I2CBus.java b/src/main/java/org/myrobotlab/i2c/I2CBus.java index 48c031d89f..b266f368f1 100644 --- a/src/main/java/org/myrobotlab/i2c/I2CBus.java +++ b/src/main/java/org/myrobotlab/i2c/I2CBus.java @@ -21,7 +21,7 @@ public class I2CBus implements Attachable, I2CBusControl { transient public final static Logger log = LoggerFactory.getLogger(I2CBus.class); String name; - // transient too help prevent infinite recursion in gson + // transient too help prevent infinite recursion in serialization transient I2CBusController controller; public I2CBus(String Name) { diff --git a/src/main/java/org/myrobotlab/process/InProcessCli.java b/src/main/java/org/myrobotlab/process/InProcessCli.java index eeccbf153f..0556c25222 100644 --- a/src/main/java/org/myrobotlab/process/InProcessCli.java +++ b/src/main/java/org/myrobotlab/process/InProcessCli.java @@ -154,7 +154,7 @@ public void run() { continue; } - log.info("c = {}", c); + log.debug("c = {}", c); // != 0x04 /* ctrl-d 0x04 ctrl-c 0x03 '\n' */ readLine += (char) c; @@ -304,18 +304,8 @@ public void process(String srcFullName, String cmd) { return; } - // subscribe - setup subscription - // MRLListener listener = new MRLListener(cliMsg.method, name + '@' + id, - // CodecUtils.getCallbackTopicName(cliMsg.method)); - // Message subscription = Message.createMessage(name + '@' + id, - // cliMsg.getFullName(), "addListener", listener); - String cliFullName = name + '@' + id; - /* - * if (srcFullName == null) { srcFullName = name + '@' + id; } - */ - // setup cli subscription MRLListener listener = new MRLListener(cliMsg.method, cliFullName, CodecUtils.getCallbackTopicName(cliMsg.method)); Message subscription = Message.createMessage(cliFullName, cliMsg.getFullName(), "addListener", listener); @@ -348,7 +338,12 @@ public void process(String srcFullName, String cmd) { * @return message */ public Message cliToMsg(String data) { - return CodecUtils.cliToMsg(contextPath, "runtime@" + id, "runtime@" + remoteId, data); + + if (contextPath != null) { + data = contextPath + data; + } + Message msg = CodecUtils.pathToMsg("runtime@" + id, data); + return CodecUtils.decodeMessageParams(msg); } public void writeToJson(Object o) { diff --git a/src/main/java/org/myrobotlab/service/Clock.java b/src/main/java/org/myrobotlab/service/Clock.java index df43ed6745..49acdbc31a 100644 --- a/src/main/java/org/myrobotlab/service/Clock.java +++ b/src/main/java/org/myrobotlab/service/Clock.java @@ -227,13 +227,18 @@ public void restartClock() { public static void main(String[] args) throws Exception { try { - WebGui webgui = (WebGui)Runtime.create("webgui", "WebGui"); - webgui.autoStartBrowser(false); - webgui.setPort(8887); - webgui.startService(); +// WebGui webgui = (WebGui)Runtime.create("webgui", "WebGui"); +// webgui.autoStartBrowser(false); +// webgui.setPort(8887); +// webgui.startService(); Clock c1 = (Clock) Runtime.start("c1", "Clock"); c1.startClock(); + + boolean done = true; + if (done) return; + + Runtime.getInstance().connect("ws://localhost:8888"); c1.stopClock(); diff --git a/src/main/java/org/myrobotlab/service/GoogleSearch.java b/src/main/java/org/myrobotlab/service/GoogleSearch.java index 749adf3263..f21ab6872a 100644 --- a/src/main/java/org/myrobotlab/service/GoogleSearch.java +++ b/src/main/java/org/myrobotlab/service/GoogleSearch.java @@ -190,7 +190,6 @@ public byte[] saveFile(String filename, byte[] data) throws IOException { return data; } - // FIXME - use gson not simpl json @Override public List imageSearch(String searchText) { diff --git a/src/main/java/org/myrobotlab/service/Mqtt.java b/src/main/java/org/myrobotlab/service/Mqtt.java index 4674fea328..ddb0a5990b 100644 --- a/src/main/java/org/myrobotlab/service/Mqtt.java +++ b/src/main/java/org/myrobotlab/service/Mqtt.java @@ -830,7 +830,7 @@ public void subscribe(String topic, int qos) throws MqttException { } String tokenToString(IMqttToken token) { - // FIXME - just gson encode it.. + StringBuffer sb = new StringBuffer(); sb.append(" MessageId:").append(token.getMessageId()); sb.append(" Response:").append(token.getResponse()); diff --git a/src/main/java/org/myrobotlab/service/MqttBroker.java b/src/main/java/org/myrobotlab/service/MqttBroker.java index a65d3df8bb..bbee5bfb81 100644 --- a/src/main/java/org/myrobotlab/service/MqttBroker.java +++ b/src/main/java/org/myrobotlab/service/MqttBroker.java @@ -169,10 +169,11 @@ public MqttBroker(String n, String id) { password = security.getKey(getName() + ".password"); } + // FIXME - more than one type of gateway ... client gateway and server gateway @Override public void connect(String uri) throws Exception { - // TODO Auto-generated method stub - + // Mqtt Brokers do not "connect" to other instances + // NOOP } public String getAddress() { @@ -339,8 +340,12 @@ public void onPublish(InterceptPublishMessage im) { // don't let broker process messages if (processApiMessages && topic.startsWith(serviceTopic)) { String mrlUri = "/" + topic.substring((serviceTopic).length()); - Message msg = CodecUtils.cliToMsg(null, getFullName(), null, mrlUri); + // FIXME - should they all be full name ? + // This will parse a topic into a json parameter message + // the message params can then be decoded with getDecodedJsonParameters + Message msg = CodecUtils.pathToMsg(getFullName(), mrlUri); String payload = m.getPayload(); + // payload takes precedence over path if (payload != null && payload.length() > 0) { msg.data = CodecUtils.decodeArray(payload); } @@ -583,6 +588,16 @@ public void unsubscribe(String mqttTopic, String callbackName, String callbackMe public static void main(String[] args) { try { LoggingFactory.init("info"); + + + Runtime.main(new String[] {"--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python"}); + + + boolean done = true; + if (done) { + return; + } + Runtime.main(new String[] { "--id", "c2"}); Python python = (Python) Runtime.start("python", "Python"); @@ -596,9 +611,13 @@ public static void main(String[] args) { Clock clock01 = (Clock) Runtime.start("clock01", "Clock"); // clock01.startClock(); + + + Mqtt mqtt = (Mqtt) Runtime.start("mqtt02", "Mqtt"); mqtt.setAutoConnect(false); + mqtt.connect("mqtt://localhost:1883"); // mqtt.connect("mqtt://test.mosquitto.org:1883"); // mqtt.publish("mrl/"); diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 1279948f1f..904a71796f 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -123,7 +123,6 @@ public Panel(String name, int x, int y, int z) { private static final long serialVersionUID = 1L; - transient protected JmDNS jmdns = null; /** @@ -311,16 +310,14 @@ public Config.Builder getNettosphereConfig() { Config.Builder configBuilder = new Config.Builder(); try { if (isSsl) { -// SelfSignedCertificate cert = new SelfSignedCertificate(); -// SslContext context = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()).build(); - - + // SelfSignedCertificate cert = new SelfSignedCertificate(); + // SslContext context = SslContextBuilder.forServer(cert.certificate(), + // cert.privateKey()).build(); + SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); - SslContext context = SslContextBuilder - .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) - .sslProvider(SslProvider.JDK).clientAuth(ClientAuth.NONE).build(); - - + SslContext context = SslContextBuilder.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()).sslProvider(SslProvider.JDK) + .clientAuth(ClientAuth.NONE).build(); + configBuilder.sslContext(context); } } catch (Exception e) { @@ -330,12 +327,12 @@ public Config.Builder getNettosphereConfig() { configBuilder.resource("/stream", stream); WebGuiConfig c = (WebGuiConfig) config; - + // add all webgui resource directories - for (String resource: c.resources) { + for (String resource : c.resources) { configBuilder.resource(resource); } - + // can't seem to make this work .mappingPath("resource/") // TO SUPPORT LEGACY - BEGIN @@ -428,10 +425,12 @@ protected void setBroadcaster(AtmosphereResource r) { /** * This method handles all http:// and ws:// requests. Depending on apiKey * which is part of initial GET - *

    + *

    + *

    * messages api attempts to promote the connection to websocket and suspends * the connection for a 2 way channel - *

    + *

    + *

    * id and session_id authentication should be required * */ @@ -500,8 +499,6 @@ public void handle(AtmosphereResource r) { log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), request.getRequestURI(), logData, uuid); } - MethodCache cache = MethodCache.getInstance(); - // important persistent connections will have associated routes ... // http/api/service requests (not persistent connections) will not // (neither will udp) @@ -537,11 +534,10 @@ public void handle(AtmosphereResource r) { } else if (apiKey.equals(CodecUtils.API_SERVICE)) { - Message msg = CodecUtils.cliToMsg( - null, - getName(), - null, - URLDecoder.decode(r.getRequest().getPathInfo(), StandardCharsets.UTF_8)); + String path = URLDecoder.decode(r.getRequest().getPathInfo(), StandardCharsets.UTF_8); + Message msg = CodecUtils.pathToMsg(getFullName(), path); + msg = CodecUtils.decodeMessageParams(msg); + // if body exists it overrides if (bodyData != null) { msg.data = CodecUtils.fromJson(bodyData, Object[].class); } @@ -549,7 +545,8 @@ public void handle(AtmosphereResource r) { if (isLocal(msg)) { // String serviceName = msg.getFullName();// getName(); // Class clazz = Runtime.getClass(serviceName); - // Object[] params = cache.getDecodedJsonParameters(clazz, msg.method, msg.data); + // Object[] params = cache.getDecodedJsonParameters(clazz, msg.method, + // msg.data); // msg.data = params; Object ret = invoke(msg); OutputStream out = r.getResponse().getOutputStream(); @@ -602,7 +599,8 @@ public void handle(AtmosphereResource r) { } // do not decode unless needed - // Object[] params = cache.getDecodedJsonParameters(clazz, msg.method, msg.data); + // Object[] params = cache.getDecodedJsonParameters(clazz, + // msg.method, msg.data); ServiceInterface si = Runtime.getService(serviceName); @@ -1157,7 +1155,7 @@ public void stopMdns() { @Override public ServiceConfig getConfig() { - WebGuiConfig config = (WebGuiConfig)super.getConfig(); + WebGuiConfig config = (WebGuiConfig) super.getConfig(); // FIXME - remove member variables use config only config.port = port; config.autoStartBrowser = autoStartBrowser; @@ -1201,21 +1199,18 @@ public static void main(String[] args) { boolean done = true; if (done) { return; - } - + } - // Runtime.start("i01", "InMoov2"); // Runtime.start("python", "Python"); // Runtime.start("i01", "InMoov2"); - + // Runtime.start("i01", "InMoov2"); Runtime.start("track", "Tracking"); // Runtime.startConfig("worky"); // Runtime.startConfig("InMoov2Head"); // Runtime.startConfig("Tracking"); - // Runtime.start("i01", "InMoov2"); // Runtime.start("python", "Python"); // Runtime.start("i01", "InMoov2"); diff --git a/src/main/java/org/myrobotlab/service/Xmpp.java b/src/main/java/org/myrobotlab/service/Xmpp.java index 15d5237520..82d0ceb964 100644 --- a/src/main/java/org/myrobotlab/service/Xmpp.java +++ b/src/main/java/org/myrobotlab/service/Xmpp.java @@ -291,7 +291,7 @@ public void processMessage(Chat chat, Message message) { try { // org.myrobotlab.framework.Message msg = // CodecUri.decodePathInfo(pathInfo); - org.myrobotlab.framework.Message msg = CodecUtils.cliToMsg(null, getName(), null, pathInfo); + org.myrobotlab.framework.Message msg = CodecUtils.pathToMsg(getName(), pathInfo); // FIXME - do the same as InProcessCli & WebGui Object ret = null; diff --git a/src/main/java/org/myrobotlab/service/meta/RuntimeMeta.java b/src/main/java/org/myrobotlab/service/meta/RuntimeMeta.java index 8b21241562..f21e872171 100644 --- a/src/main/java/org/myrobotlab/service/meta/RuntimeMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/RuntimeMeta.java @@ -23,12 +23,11 @@ public RuntimeMeta() { addDependency("ch.qos.logback", "logback-classic", "1.2.3"); includeServiceInOneJar(true); - // apache 2.0 license - addDependency("com.google.code.gson", "gson", "2.8.5"); - + // for proxy generation addDependency("net.bytebuddy", "byte-buddy", "1.12.16"); + // apache 2.0 license addDependency("com.fasterxml.jackson.core", "jackson-core", "2.14.0"); addDependency("com.fasterxml.jackson.core", "jackson-annotations", "2.14.0"); addDependency("com.fasterxml.jackson.core", "jackson-databind", "2.14.0"); diff --git a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java index 8c864ab390..7ffd26fcd4 100644 --- a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java +++ b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.bouncycastle.util.Strings; @@ -13,6 +14,7 @@ import org.junit.Test; import org.myrobotlab.framework.Platform; import org.myrobotlab.service.data.Locale; +import org.myrobotlab.service.data.Orientation; import org.myrobotlab.test.AbstractTest; public class CodecUtilsTest extends AbstractTest { @@ -63,6 +65,86 @@ public void testLocale() { "{\"language\":\"\",\"displayLanguage\":\"\",\"country\":\"US\",\"displayCountry\":\"United States\",\"tag\":\"-US\",\"class\":\"org.myrobotlab.service.data.Locale\"}", json); + } + + @Test + public void testExtractParams() { + // /runtime/connect/"http://blah:8888" + String input = null; + Object[] params = null; + double delta = 0.0001; + + input = "\"http://blah:8888\""; + params = CodecUtils.extractJsonParamsFromPath(input); + assertEquals("\"http://blah:8888\"", params[0]); + + input = "\"http://blah:8888/this/path/\""; + params = CodecUtils.extractJsonParamsFromPath(input); + assertEquals("\"http://blah:8888/this/path/\"", params[0]); + + input = "\"http://blah:8888/this/path/\"/5"; + params = CodecUtils.extractJsonParamsFromPath(input); + assertEquals("\"http://blah:8888/this/path/\"", params[0]); + assertEquals("5", params[1]); + + input = "[3,5,7,8]/[1.0, 2.0, 3.0]/true"; + params = CodecUtils.extractJsonParamsFromPath(input); + List list = (List)CodecUtils.fromJson((String)params[0]); + assertEquals(8, list.get(3)); + list = (List)CodecUtils.fromJson((String)params[1]); + assertEquals(3.0,(double)list.get(2), delta); + assertEquals(true, (boolean)CodecUtils.fromJson((String)params[2])); + + input = "[\"apple\",\"banana\",\"orange\"]/[\"a\",\"b\",\"c\"]/true"; + params = CodecUtils.extractJsonParamsFromPath(input); + list = (List)CodecUtils.fromJson((String)params[0]); + assertEquals("apple",list.get(0)); + list = (List)CodecUtils.fromJson((String)params[1]); + assertEquals("a", list.get(0)); + assertEquals(true, (boolean)CodecUtils.fromJson((String)params[2])); + + String[] files =new String[] {"f:\\testdir\\blah","/root/","/home/mydir/blah"}; + String[] abc = new String[] {"a", "b", "c"}; + + input = CodecUtils.toJson(files) + "/" + CodecUtils.toJson(abc) + "/" + CodecUtils.toJson(true); + + // input = "\"[\"f:\\testdir\\blah\",\"/root/\",\"/home/mydir/blah\"]\"/\"[\"a\",\"b\",\"c\"]\"/true"; + params = CodecUtils.extractJsonParamsFromPath(input); + assertEquals(3, params.length); + list = (List)CodecUtils.fromJson((String)params[0]); + assertEquals("/root/", list.get(1)); + // String[] files = CodecUtils.fromJson(params[0], String[].class); + // assertEquals("\"[\"apple\",\"banana\",\"orange\"]\"", CodecUtils.fromJson(params[0], String[].class)); + Orientation o = new Orientation(); + o.pitch = 2.2342; + o.yaw = 1.234; + o.roll = 0.343; + + input = CodecUtils.toJson(files) + "/" + CodecUtils.toJson(o) + "/" + CodecUtils.toJson(true); + params = CodecUtils.extractJsonParamsFromPath(input); + assertEquals(3, params.length); + Map t = (Map)CodecUtils.fromJson((String)params[1]); + + assertEquals(2.2342, (double)t.get("pitch"), delta); + + input = "This is invalid json /and a block of/ text between/and/a single character/ ."; + params = CodecUtils.extractJsonParamsFromPath(input); + assertEquals(6, params.length); + boolean strict = false; + try { + CodecUtils.fromJson(input); + } catch(Exception e) { + // strict is required + strict = true; + } + assertTrue(strict); + + input = "\"This is valid json /and a block of\"/\" text between/and/a single character/ .\""; + params = CodecUtils.extractJsonParamsFromPath(input); + assertEquals(2, params.length); + assertEquals("This is valid json /and a block of", CodecUtils.fromJson((String)params[0])); + + } @Test diff --git a/src/test/java/org/myrobotlab/codec/json/GsonPolymorphicTest.java b/src/test/java/org/myrobotlab/codec/json/GsonPolymorphicTest.java deleted file mode 100644 index 9233583688..0000000000 --- a/src/test/java/org/myrobotlab/codec/json/GsonPolymorphicTest.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.myrobotlab.codec.json; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.Registration; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.test.AbstractTest; -import org.slf4j.Logger; - -import java.io.Serializable; -import java.util.Arrays; - -public class GsonPolymorphicTest extends AbstractTest { - public final static Logger log = LoggerFactory.getLogger(GsonPolymorphicTest.class); - - private static Gson polymorphicGson; - - private static Gson regularGson; - - @BeforeClass - public static void setup() { - polymorphicGson = new GsonBuilder().registerTypeAdapterFactory(new GsonPolymorphicTypeAdapterFactory()) - .setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").disableHtmlEscaping().create(); - - - regularGson = new GsonBuilder() - .setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").disableHtmlEscaping().create(); - } - - @Test - public void testStringSer() { - String testString = "this is a test with spaces and $pecial characters!"; - String jsonString = polymorphicGson.toJson(testString); - log.debug("Encoded test string: " + jsonString); - String decodedString = regularGson.fromJson(jsonString, String.class); - log.debug("Decoded test string: " + decodedString); - Assert.assertEquals("String encoding not correct", testString, decodedString); - } - - @Test - public void testStringArraySer() { - String[] testStrings = new String[] {"This", "is", "a", "test", "array"}; - String jsonString = polymorphicGson.toJson(testStrings); - log.debug("Encoded test string: " + jsonString); - String[] decodedStrings = regularGson.fromJson(jsonString, String[].class); - log.debug("Decoded test strings: " + Arrays.toString(decodedStrings)); - Assert.assertArrayEquals("String array encoding incorrect", testStrings, decodedStrings); - } - - @Test - public void testNumberSer() { - int testInt = 42; - String jsonString = polymorphicGson.toJson(testInt); - log.debug("Encoded test string: " + jsonString); - int decodedInt = regularGson.fromJson(jsonString, Integer.class); - log.debug("Decoded test int: " + decodedInt); - Assert.assertEquals("Int encoding not correct", testInt, decodedInt); - } - - @Test - public void testBoolSer() { - boolean testBoolean = false; - String jsonString = polymorphicGson.toJson(testBoolean); - log.debug("Encoded test string: " + jsonString); - boolean decodedBoolean = regularGson.fromJson(jsonString, Boolean.class); - log.debug("Decoded test string: " + decodedBoolean); - Assert.assertEquals("Boolean encoding not correct", testBoolean, decodedBoolean); - } - - - @Test - public void testStringDeser() { - String testString = "this is a test with spaces and $pecial characters!"; - String jsonString = regularGson.toJson(testString); - log.debug("Encoded test string: " + jsonString); - String decodedString = polymorphicGson.fromJson(jsonString, String.class); - log.debug("Decoded test string: " + decodedString); - Assert.assertEquals("String decoding not correct", testString, decodedString); - } - - @Test - public void testStringArrayDeser() { - String[] testStrings = new String[] {"This", "is", "a", "test", "array"}; - String jsonString = regularGson.toJson(testStrings); - log.debug("Encoded test string: " + jsonString); - String[] decodedStrings = polymorphicGson.fromJson(jsonString, String[].class); - log.debug("Decoded test strings: " + Arrays.toString(decodedStrings)); - Assert.assertArrayEquals("String array decoding incorrect", testStrings, decodedStrings); - } - - @Test - public void testNumberDeser() { - int testInt = 42; - String jsonString = regularGson.toJson(testInt); - log.debug("Encoded test string: " + jsonString); - int decodedInt = polymorphicGson.fromJson(jsonString, Integer.class); - log.debug("Decoded test int: " + decodedInt); - Assert.assertEquals("Int decoding not correct", testInt, decodedInt); - } - - @Test - public void testBoolDeser() { - boolean testBoolean = false; - String jsonString = regularGson.toJson(testBoolean); - log.debug("Encoded test string: " + jsonString); - - boolean decodedBoolean = polymorphicGson.fromJson(jsonString, Boolean.class); - log.debug("Decoded test string: " + decodedBoolean); - Assert.assertEquals("Boolean decoding not correct", testBoolean, decodedBoolean); - } - - @Test - public void testApiDescriptionDeser() { - CodecUtils.ApiDescription description = new CodecUtils.ApiDescription("key", - "/path/", "{exampleURI}", "This is a description"); - String jsonString = regularGson.toJson(description); - log.debug("Encoded test string: " + jsonString); - CodecUtils.ApiDescription decodedDescription = polymorphicGson.fromJson(jsonString, CodecUtils.ApiDescription.class); - log.debug("Decoded test string: " + decodedDescription); - Assert.assertEquals("ApiDescription decoding not correct", description, decodedDescription); - } - -} diff --git a/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java b/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java index 07682d6f1f..07e1775110 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java @@ -1,6 +1,7 @@ package org.myrobotlab.service; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; @@ -32,12 +33,12 @@ public void cliTest() throws Exception { // from ,to null=runtime, data String cwd = null; - Message msg = CodecUtils.cliToMsg(cwd, getName(), null, "ls"); + Message msg = CodecUtils.pathToMsg(getName(), "ls"); assertEquals("runtime", msg.getName()); assertEquals("ls", msg.method); assertEquals(getName(), msg.getSrcName()); - msg = CodecUtils.cliToMsg(null, getName() + "@someWhere", null, "ls"); + msg = CodecUtils.pathToMsg(getName() + "@someWhere", "ls"); assertEquals(getName(), msg.getSrcName()); assertEquals("someWhere", msg.getSrcId()); assertEquals(getName() + "@someWhere", msg.getSrcFullName()); @@ -46,21 +47,24 @@ public void cliTest() throws Exception { // Message msg = CodecUtils.cliToMsg(null, getName(), null, "/ls /runtime"); // FAILS - msg = CodecUtils.cliToMsg(cwd, getName() + "@someWhere", "blah@far", "ls"); - assertEquals("blah", msg.getName()); - assertEquals("blah@far", msg.getFullName()); - assertEquals("far", msg.getId()); + msg = CodecUtils.pathToMsg(getName() + "@someWhere", "ls"); + assertEquals("runtime", msg.getName()); + assertEquals("runtime", msg.getFullName()); + assertEquals(getName() + "@someWhere", msg.sender); + assertNull(msg.getId()); + assertEquals(0, msg.data.length); - cwd = "/"; - msg = CodecUtils.cliToMsg(cwd, getName(), null, "ls"); + cwd = "/runtime/"; + msg = CodecUtils.pathToMsg(getName(), cwd + "ls"); assertEquals("runtime", msg.getName()); assertEquals("ls", msg.method); assertEquals(getName(), msg.getSrcName()); + assertNull(msg.data); - cwd = "/blah"; - msg = CodecUtils.cliToMsg(cwd, getName(), null, "method"); + cwd = "/runtime/blahmethod"; + msg = CodecUtils.pathToMsg(getName(), cwd); assertEquals("runtime", msg.getName()); - assertEquals("ls", msg.method); + assertEquals("blahmethod", msg.method); assertEquals(getName(), msg.getSrcName()); // make sure runtime is running diff --git a/src/test/java/org/myrobotlab/service/WebGuiTest.java b/src/test/java/org/myrobotlab/service/WebGuiTest.java index fe9ec168be..643485fc0c 100644 --- a/src/test/java/org/myrobotlab/service/WebGuiTest.java +++ b/src/test/java/org/myrobotlab/service/WebGuiTest.java @@ -4,18 +4,14 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.util.List; import org.junit.Before; import org.junit.Test; import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.MRLListener; -import org.atmosphere.cpr.AtmosphereResource; -import org.atmosphere.cpr.AtmosphereResourceImpl; -import org.junit.Before; -import org.junit.Test; -import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.TimeoutException; import org.myrobotlab.logging.LoggerFactory; @@ -52,9 +48,9 @@ public void getTest() { } @Test - public void getTestWithParameter() { + public void getTestWithParameter() throws UnsupportedEncodingException { - byte[] bytes = Http.get("http://localhost:8889/api/service/runtime/isLocal/runtime"); + byte[] bytes = Http.get("http://localhost:8889/api/service/runtime/isLocal/%22runtime%22"); assertNotNull(bytes); String ret = new String(bytes); assertTrue(ret.contains("true")); @@ -148,14 +144,6 @@ public void urlEncodingTest() { assertEquals("true", ret); } - @Test - public void noQuotesTest() { - //exec(print) - byte[] bytes = Http.get("http://localhost:8889/api/service/pythonApiTest/exec/print"); - String ret = new String(bytes); - assertEquals("true", ret); - } - @Test public void sendBlockingTest() throws InterruptedException, TimeoutException { String retVal = "retVal"; diff --git a/src/test/java/org/myrobotlab/service/data/LocaleTest.java b/src/test/java/org/myrobotlab/service/data/LocaleTest.java index 62558fa29b..c76536be58 100644 --- a/src/test/java/org/myrobotlab/service/data/LocaleTest.java +++ b/src/test/java/org/myrobotlab/service/data/LocaleTest.java @@ -1,8 +1,7 @@ package org.myrobotlab.service.data; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import org.junit.Test; +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.test.AbstractTest; import java.util.HashMap; @@ -128,20 +127,18 @@ public void testJavaLocale() { // java.util.Locale.setDefault(t); java.util.Locale[] locales = java.util.Locale.getAvailableLocales(); - String[] isoLanguages = java.util.Locale.getISOLanguages(); - String[] isoCountries = java.util.Locale.getISOCountries(); // is java.util.Locale serializable - Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").setPrettyPrinting().disableHtmlEscaping().create(); - String json = gson.toJson(locale); - java.util.Locale l = gson.fromJson(json, java.util.Locale.class); + String json = CodecUtils.toJson(locale); + + java.util.Locale l = CodecUtils.fromJson(json, java.util.Locale.class); java.util.Locale br = new java.util.Locale("en", "BR", "variant"); log.info("br getLanguage - {}", br.getLanguage()); log.info("br getDisplayLanguage - {}", br.getDisplayLanguage()); br.toLanguageTag(); - json = gson.toJson(br); - l = gson.fromJson(json, java.util.Locale.class); + json = CodecUtils.toJson(br); + l = CodecUtils.fromJson(json, java.util.Locale.class); br.equals(l); l = new java.util.Locale("en_BR_variant"); diff --git a/src/test/java/org/myrobotlab/service/interfaces/SpeechSynthesisTest.java b/src/test/java/org/myrobotlab/service/interfaces/SpeechSynthesisTest.java index 68ef3147af..034b5c8d23 100644 --- a/src/test/java/org/myrobotlab/service/interfaces/SpeechSynthesisTest.java +++ b/src/test/java/org/myrobotlab/service/interfaces/SpeechSynthesisTest.java @@ -7,6 +7,7 @@ import org.junit.Ignore; import org.junit.Test; +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.repo.ServiceData; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; @@ -14,9 +15,6 @@ import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis.Voice; import org.slf4j.Logger; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - // TODO: this unit test is so loud! we need a way to run it in mute mode // also it's long based on the length of the audio being generated/played. // TODO: find a way to validate that the mp3s are created, but don't actually play them. (checksum? file length? other approach?) @@ -49,20 +47,17 @@ public static void main(String[] args) { // Locale.setDefault(t); Locale[] locales = Locale.getAvailableLocales(); - String[] isoLanguages = Locale.getISOLanguages(); - String[] isoCountries = Locale.getISOCountries(); // is Locale serializable - Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").setPrettyPrinting().disableHtmlEscaping().create(); - String json = gson.toJson(locale); - Locale l = gson.fromJson(json, Locale.class); + String json = CodecUtils.toJson(locale); + Locale l = CodecUtils.fromJson(json, Locale.class); Locale br = new Locale("en", "BR", "variant"); log.info("br getLanguage - {}", br.getLanguage()); log.info("br getDisplayLanguage - {}", br.getDisplayLanguage()); br.toLanguageTag(); - json = gson.toJson(br); - l = gson.fromJson(json, Locale.class); + json = CodecUtils.toJson(br); + l = CodecUtils.fromJson(json, Locale.class); br.equals(l); l = new Locale("en_BR_variant"); From 8e13c0dbcb5d5979a65cea0bfb5d596c2dcfdf9e Mon Sep 17 00:00:00 2001 From: grog Date: Sun, 6 Aug 2023 08:40:47 -0700 Subject: [PATCH 34/54] fix for when libraries/serviceData.json fails to load --- .../framework/repo/ServiceData.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/repo/ServiceData.java b/src/main/java/org/myrobotlab/framework/repo/ServiceData.java index 3ab071714d..d1f6818449 100644 --- a/src/main/java/org/myrobotlab/framework/repo/ServiceData.java +++ b/src/main/java/org/myrobotlab/framework/repo/ServiceData.java @@ -177,18 +177,17 @@ static public ServiceData getLocalInstance() { // First check the libraries/serviceData.json dir. File jsonFile = new File(serviceDataCacheFileName); if (jsonFile.exists()) { - // load it and return! - String data = null; try { - data = FileIO.toString(jsonFile); - } catch (IOException e) { + // load it and return! + localInstance = CodecUtils.fromJson(FileIO.toString(jsonFile), ServiceData.class); + log.info("returning cached serviceData.json from {}", jsonFile); + + } catch (Exception e) { log.warn("Error reading serviceData.json from location {}", jsonFile.getAbsolutePath()); - } - localInstance = CodecUtils.fromJson(data, ServiceData.class); - log.info("Returning serviceData.json from {}", jsonFile); - return localInstance; - } else { - + } + } + + if (localInstance == null){ // we are running in an IDE and haven't generated/saved the // serviceData.json yet. try { @@ -201,9 +200,10 @@ static public ServiceData getLocalInstance() { log.error("Unable to generate the serivceData.json file!!"); // This is a fatal issue. I think we should exit the jvm here. } - return localInstance; - } + + return localInstance; + } } From 4e5b4ba9a5233037d79df304eb7d8bc86e008ddf Mon Sep 17 00:00:00 2001 From: Branden Butler Date: Mon, 7 Aug 2023 14:27:02 -0500 Subject: [PATCH 35/54] Fix non-transient package-private CvRect in SetImageROI filter (#1329) --- .../java/org/myrobotlab/opencv/OpenCVFilterSetImageROI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilterSetImageROI.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilterSetImageROI.java index fd22d58104..d97788b9ab 100644 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilterSetImageROI.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilterSetImageROI.java @@ -38,7 +38,7 @@ public class OpenCVFilterSetImageROI extends OpenCVFilter { private static final long serialVersionUID = 1L; - CvRect rect = null; + private transient CvRect rect = null; public final static Logger log = LoggerFactory.getLogger(OpenCVFilterSetImageROI.class); public OpenCVFilterSetImageROI(String name) { From 244d6a7a4d41acaee79daddfd37f1dd198763972 Mon Sep 17 00:00:00 2001 From: grog Date: Wed, 9 Aug 2023 06:44:10 -0700 Subject: [PATCH 36/54] relaxed parsing --- src/main/java/org/myrobotlab/codec/CodecUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index 77756b5e2d..899ea8c346 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -219,7 +219,7 @@ public class CodecUtils { mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); // Make jackson behave such that unknown properties are ignored - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } From 516f1d5ba68600379b749d311f1a1123adc59316 Mon Sep 17 00:00:00 2001 From: GroG Date: Sat, 12 Aug 2023 09:14:32 -0700 Subject: [PATCH 37/54] Boilerplate config changes 2 (#1328) * added service-life-cycle doc * proposal * different implementation * worky proposal * testing and removal of local config member * unit test fixed and audiofile updated * Rework config generics so T is propagated through ConfigurableService correctly * stop compiler errors * all service templated with config * unit tests catches bug before release ... yesss.. * more fixes * dumb unit test fix * requested updates * removed casts fixed servo ui * fixed unit test --------- Co-authored-by: Branden Butler --- doc/service-life-cycle.md | 52 +++++++ .../java/org/myrobotlab/codec/CodecUtils.java | 10 -- .../document/connector/AbstractConnector.java | 4 +- .../org/myrobotlab/framework/Service.java | 139 ++++++++---------- .../java/org/myrobotlab/framework/Task.java | 2 +- .../interfaces/ConfigurableService.java | 8 + .../interfaces/ServiceInterface.java | 27 ++-- .../service/Adafruit16CServoDriver.java | 10 +- .../myrobotlab/service/AdafruitIna219.java | 3 +- .../service/AdafruitMotorHat4Pi.java | 3 +- .../java/org/myrobotlab/service/Ads1115.java | 10 +- .../org/myrobotlab/service/Amt203Encoder.java | 3 +- .../java/org/myrobotlab/service/Android.java | 3 +- .../java/org/myrobotlab/service/Arduino.java | 17 +-- src/main/java/org/myrobotlab/service/Arm.java | 3 +- .../myrobotlab/service/As5048AEncoder.java | 3 +- .../org/myrobotlab/service/AudioCapture.java | 3 +- .../org/myrobotlab/service/AudioFile.java | 15 +- .../myrobotlab/service/AzureTranslator.java | 3 +- .../myrobotlab/service/BeagleBoardBlack.java | 3 +- .../java/org/myrobotlab/service/Blender.java | 3 +- .../java/org/myrobotlab/service/Blocks.java | 3 +- .../java/org/myrobotlab/service/Bno055.java | 3 +- .../java/org/myrobotlab/service/BoofCv.java | 3 +- .../java/org/myrobotlab/service/Chassis.java | 3 +- .../org/myrobotlab/service/ChessGame.java | 3 +- .../java/org/myrobotlab/service/Clock.java | 37 +++-- .../java/org/myrobotlab/service/Cron.java | 16 +- .../java/org/myrobotlab/service/Database.java | 3 +- .../myrobotlab/service/Deeplearning4j.java | 3 +- .../org/myrobotlab/service/DiscordBot.java | 23 +-- .../java/org/myrobotlab/service/DiyServo.java | 3 +- .../java/org/myrobotlab/service/Docker.java | 6 +- .../myrobotlab/service/DocumentPipeline.java | 3 +- .../org/myrobotlab/service/DruppNeck.java | 3 +- .../myrobotlab/service/EddieControlBoard.java | 3 +- .../org/myrobotlab/service/Elasticsearch.java | 3 +- .../java/org/myrobotlab/service/Email.java | 25 ++-- .../java/org/myrobotlab/service/Emoji.java | 2 +- .../java/org/myrobotlab/service/Esp8266.java | 3 +- .../org/myrobotlab/service/Esp8266_01.java | 3 +- .../service/FiniteStateMachine.java | 18 +-- src/main/java/org/myrobotlab/service/Git.java | 3 +- .../java/org/myrobotlab/service/GoPro.java | 3 +- .../org/myrobotlab/service/GoogleCloud.java | 3 +- .../org/myrobotlab/service/GoogleSearch.java | 3 +- .../myrobotlab/service/GoogleTranslate.java | 3 +- src/main/java/org/myrobotlab/service/Gps.java | 3 +- .../java/org/myrobotlab/service/Gpt3.java | 2 +- .../java/org/myrobotlab/service/Hd44780.java | 10 +- .../org/myrobotlab/service/HtmlFilter.java | 2 +- .../org/myrobotlab/service/HtmlParser.java | 3 +- .../org/myrobotlab/service/HttpClient.java | 3 +- .../java/org/myrobotlab/service/I2cMux.java | 6 +- .../java/org/myrobotlab/service/IBus.java | 3 +- .../org/myrobotlab/service/ImageDisplay.java | 7 +- .../java/org/myrobotlab/service/InMoov2.java | 6 +- .../org/myrobotlab/service/InMoov2Arm.java | 4 +- .../org/myrobotlab/service/InMoov2Hand.java | 3 +- .../org/myrobotlab/service/InMoov2Head.java | 4 +- .../org/myrobotlab/service/InMoov2Torso.java | 4 +- .../org/myrobotlab/service/IndianTts.java | 3 +- .../service/IntegratedMovement.java | 3 +- .../java/org/myrobotlab/service/Intro.java | 3 +- .../myrobotlab/service/InverseKinematics.java | 3 +- .../service/InverseKinematics3D.java | 3 +- .../java/org/myrobotlab/service/IpCamera.java | 3 +- .../java/org/myrobotlab/service/JFugue.java | 3 +- .../org/myrobotlab/service/JMonkeyEngine.java | 6 +- .../org/myrobotlab/service/JavaScript.java | 3 +- .../java/org/myrobotlab/service/Joystick.java | 11 +- .../myrobotlab/service/KafkaConnector.java | 3 +- .../java/org/myrobotlab/service/Keyboard.java | 3 +- .../org/myrobotlab/service/KeyboardSim.java | 3 +- .../org/myrobotlab/service/LeapMotion.java | 29 ++-- .../org/myrobotlab/service/LeapMotion2.java | 2 +- .../java/org/myrobotlab/service/Lidar.java | 3 +- .../org/myrobotlab/service/LidarVlp16.java | 3 +- .../java/org/myrobotlab/service/Lloyd.java | 3 +- .../java/org/myrobotlab/service/Lm75a.java | 3 +- .../org/myrobotlab/service/LocalSpeech.java | 18 +-- src/main/java/org/myrobotlab/service/Log.java | 3 +- .../java/org/myrobotlab/service/Mail.java | 3 +- .../org/myrobotlab/service/MarySpeech.java | 3 +- .../java/org/myrobotlab/service/Maven.java | 3 +- .../myrobotlab/service/MobilePlatform.java | 3 +- .../java/org/myrobotlab/service/Motor.java | 11 +- .../org/myrobotlab/service/MotorDualPwm.java | 13 +- .../org/myrobotlab/service/MotorHat4Pi.java | 11 +- .../org/myrobotlab/service/MotorPort.java | 2 +- .../java/org/myrobotlab/service/MouseSim.java | 3 +- .../org/myrobotlab/service/MouthControl.java | 10 +- .../java/org/myrobotlab/service/Mpr121.java | 12 +- .../java/org/myrobotlab/service/Mpu6050.java | 8 +- .../java/org/myrobotlab/service/Mqtt.java | 3 +- .../org/myrobotlab/service/MqttBroker.java | 22 +-- .../java/org/myrobotlab/service/MultiWii.java | 3 +- .../org/myrobotlab/service/MyoThalmic.java | 3 +- .../java/org/myrobotlab/service/NeoPixel.java | 2 +- .../java/org/myrobotlab/service/OakD.java | 3 +- .../org/myrobotlab/service/OculusDiy.java | 3 +- .../org/myrobotlab/service/OculusRift.java | 3 +- .../org/myrobotlab/service/OledSsd1306.java | 3 +- .../java/org/myrobotlab/service/OpenCV.java | 11 +- .../java/org/myrobotlab/service/OpenNi.java | 3 +- .../myrobotlab/service/OpenWeatherMap.java | 11 +- src/main/java/org/myrobotlab/service/Osc.java | 3 +- .../java/org/myrobotlab/service/Pcf8574.java | 10 +- src/main/java/org/myrobotlab/service/Pid.java | 10 +- .../java/org/myrobotlab/service/Pingdar.java | 3 +- src/main/java/org/myrobotlab/service/Pir.java | 16 +- .../java/org/myrobotlab/service/Polly.java | 5 +- .../org/myrobotlab/service/ProgramAB.java | 75 ++++------ .../java/org/myrobotlab/service/Py4j.java | 3 +- .../java/org/myrobotlab/service/Python.java | 47 +++--- .../java/org/myrobotlab/service/Random.java | 14 +- .../java/org/myrobotlab/service/RasPi.java | 2 +- .../org/myrobotlab/service/Rekognition.java | 3 +- .../java/org/myrobotlab/service/Relay.java | 3 +- .../java/org/myrobotlab/service/RoboClaw.java | 3 +- .../java/org/myrobotlab/service/Roomba.java | 3 +- src/main/java/org/myrobotlab/service/Ros.java | 10 +- .../java/org/myrobotlab/service/Runtime.java | 76 ++++++---- .../org/myrobotlab/service/Sabertooth.java | 11 +- .../java/org/myrobotlab/service/Security.java | 15 +- .../myrobotlab/service/SegmentDisplay.java | 3 +- .../org/myrobotlab/service/SensorMonitor.java | 3 +- .../java/org/myrobotlab/service/Serial.java | 10 +- .../org/myrobotlab/service/SerialRelay.java | 3 +- .../java/org/myrobotlab/service/Servo.java | 7 +- .../org/myrobotlab/service/ServoMixer.java | 2 +- .../java/org/myrobotlab/service/Shoutbox.java | 3 +- .../java/org/myrobotlab/service/SlackBot.java | 8 +- .../java/org/myrobotlab/service/Solr.java | 18 +-- .../java/org/myrobotlab/service/Sphinx.java | 2 +- .../org/myrobotlab/service/SpotMicro.java | 13 +- .../java/org/myrobotlab/service/Test.java | 3 +- .../myrobotlab/service/UltrasonicSensor.java | 8 +- .../org/myrobotlab/service/VideoStreamer.java | 3 +- .../myrobotlab/service/VirtualArduino.java | 3 +- .../java/org/myrobotlab/service/WebGui.java | 26 ++-- .../service/WebSocketConnector.java | 3 +- .../java/org/myrobotlab/service/Webcam.java | 3 +- .../service/WebkitSpeechSynthesis.java | 3 +- src/main/java/org/myrobotlab/service/Wii.java | 3 +- .../org/myrobotlab/service/Wikipedia.java | 3 +- .../java/org/myrobotlab/service/WorkE.java | 3 +- .../java/org/myrobotlab/service/Xmpp.java | 7 +- .../myrobotlab/service/_TemplateService.java | 7 +- .../abstracts/AbstractComputerVision.java | 3 +- .../abstracts/AbstractMicrocontroller.java | 3 +- .../service/abstracts/AbstractMotor.java | 44 +++--- .../abstracts/AbstractMotorController.java | 3 +- .../service/abstracts/AbstractPinEncoder.java | 3 +- .../service/abstracts/AbstractServo.java | 63 ++++---- .../abstracts/AbstractSpeechRecognizer.java | 12 +- .../abstracts/AbstractSpeechSynthesis.java | 14 +- .../service/abstracts/AbstractVideoSink.java | 3 +- .../abstracts/AbstractVideoSource.java | 3 +- .../service/config/EmojiConfig.java | 1 - .../service/config/SabertoothConfig.java | 2 +- .../org/myrobotlab/string/StringUtil.java | 10 ++ .../WebGui/app/service/js/ServoGui.js | 4 + .../org/myrobotlab/framework/ServiceTest.java | 18 ++- .../org/myrobotlab/service/ServoTest.java | 1 + 165 files changed, 774 insertions(+), 706 deletions(-) create mode 100644 doc/service-life-cycle.md create mode 100644 src/main/java/org/myrobotlab/framework/interfaces/ConfigurableService.java diff --git a/doc/service-life-cycle.md b/doc/service-life-cycle.md new file mode 100644 index 0000000000..8fa5b4ebdb --- /dev/null +++ b/doc/service-life-cycle.md @@ -0,0 +1,52 @@ +# Service Life Cycle +```mermaid +stateDiagram + + start: start(name, type) + load: load(name, type) + loadService: loadService(plan, name, type, true, 0) + + [*] --> start + + start --> load + load --> loadService + loadService --> getDefault + getDefault --> readServiceConfig + readServiceConfig --> loadService + loadService --> createServicesFromPlan + createServicesFromPlan --> createService + createService --> setConfig + setConfig --> apply + apply --> startService + startService --> stopService + stopService --> releaseService + releaseService --> release + release --> [*] + + [*] --> create + create --> load + +``` + +### start(name, type) +Creates and starts a service with the given name and type + +### load +Starts loading the hierarchy of configuration +FIXME - this should not be the memory plan, but should exist on the filesystem +Default config is used if no config found ~~in memory~~ on filesystem + +### loadService +Recursively loads a service config into a plan + +### createServicesFromPlan(plan, createdServices, name) +Loops through all "loaded" services in the plan and creates them all + +### createService +Instantiates instance of service + +### setConfig +Sets the config of the service + +### apply +Applies the config to the service \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index 899ea8c346..6b37b525ee 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -1619,14 +1619,4 @@ public static byte[] fromBase64(String input) { return Base64.getDecoder().decode(input); } - public static String removeEnd(final String str, final String remove) { - if (str == null || str.length() == 0 || remove == null || remove.length() == 0) { - return str; - } - if (str.endsWith(remove)) { - return str.substring(0, str.length() - remove.length()); - } - return str; - } - } diff --git a/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java b/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java index b008cf6be4..d49982478d 100644 --- a/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java +++ b/src/main/java/org/myrobotlab/document/connector/AbstractConnector.java @@ -7,8 +7,8 @@ import org.myrobotlab.document.Document; import org.myrobotlab.document.transformer.ConnectorConfig; import org.myrobotlab.framework.Service; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.DocumentConnector; -import org.myrobotlab.service.interfaces.DocumentListener; import org.myrobotlab.service.interfaces.DocumentPublisher; /** @@ -17,7 +17,7 @@ * service. * */ -public abstract class AbstractConnector extends Service implements DocumentPublisher, DocumentConnector { +public abstract class AbstractConnector extends Service implements DocumentPublisher, DocumentConnector { private static final long serialVersionUID = 1L; protected ConnectorState state = ConnectorState.STOPPED; diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index f0c83daa16..fe7dfd9450 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -58,6 +58,7 @@ import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.framework.interfaces.Broadcaster; +import org.myrobotlab.framework.interfaces.ConfigurableService; import org.myrobotlab.framework.interfaces.FutureInvoker; import org.myrobotlab.framework.interfaces.NameProvider; import org.myrobotlab.framework.interfaces.ServiceInterface; @@ -73,6 +74,7 @@ import org.myrobotlab.service.interfaces.AuthorizationProvider; import org.myrobotlab.service.interfaces.QueueReporter; import org.myrobotlab.service.meta.abstracts.MetaData; +import org.myrobotlab.string.StringUtil; import org.slf4j.Logger; /** @@ -86,7 +88,7 @@ * messages. * */ -public abstract class Service implements Runnable, Serializable, ServiceInterface, Broadcaster, QueueReporter, FutureInvoker { +public abstract class Service implements Runnable, Serializable, ServiceInterface, Broadcaster, QueueReporter, FutureInvoker, ConfigurableService { // FIXME upgrade to ScheduledExecutorService // http://howtodoinjava.com/2015/03/25/task-scheduling-with-executors-scheduledthreadpoolexecutor-example/ @@ -99,6 +101,12 @@ public abstract class Service implements Runnable, Serializable, ServiceInterfac */ protected MetaData serviceType; + /** + * Config member - configuration of type {ServiceType}Config + * Runtime applys either the default config or a saved config during service creation + */ + protected T config; + private static final long serialVersionUID = 1L; transient public final static Logger log = LoggerFactory.getLogger(Service.class); @@ -145,11 +153,6 @@ public abstract class Service implements Runnable, Serializable, ServiceInterfac */ protected Properties defaultLocalization = null; - /** - * the last config applied to this service - */ - protected ServiceConfig config; - /** * map of keys to localizations - * @@ -483,7 +486,7 @@ static public String getResourceDir(String serviceType, String additionalPath) { // stupid solution to get past static problem if (!"Runtime".equals(serviceType)) { - resourceDir = ((RuntimeConfig)Runtime.getInstance().getConfig()).resource + fs + serviceType; + resourceDir = Runtime.getInstance().getConfig().resource + fs + serviceType; } else { resourceDir = "resource"; } @@ -1127,10 +1130,10 @@ public boolean hasError() { @Override public Map getPeers() { - if (config == null) { + if (getConfig() == null) { return null; } - return config.getPeers(); + return getConfig().getPeers(); } /** @@ -1155,10 +1158,10 @@ public String getPeerKey(String name) { @Override public Set getPeerKeys() { - if (config == null || config.peers == null) { + if (getConfig() == null || getConfig().peers == null) { return new HashSet<>(); } - return config.peers.keySet(); + return getConfig().peers.keySet(); } public String help(String format, String level) { @@ -1395,47 +1398,50 @@ public boolean isRunning() { } /** - * Default load config method, subclasses should override this to support - * service specific configuration in the service yaml files. - * - * apply is the first function to be called after construction of a service, - * then startService will be called - * - * construct -> apply -> startService - * + * getConfig returns current config of the service. This default super method + * will also filter webgui subscriptions out, in addition for any local subscriptions it + * will remove the instance "id" from any service. The reason it removes the webgui + * subscriptions is to avoid overwelming the user when modifying config. UI subscriptions + * tend to be very numerous and not very useful to the user. The reason it removes the + * instance id from local subscriptions is to allow the config to be used with any instance. + * Unless the user is controlling instance id, its random every restart. */ - @Override - public ServiceConfig apply(ServiceConfig inConfig) { - log.info("Default service config loading for service: {} type: {}", getName(), getTypeKey()); - /* - * We clone/serialize here because we don't want to use the same reference - * of of config in the plan. If configuration is applied through the plan, - * "or from anywhere else" we make a copy of it here. And the copy is - * applied to the actual service. This keeps the plan safe to modify without - * the worry of modifying a running service config. - */ + public T getConfig() { + return config; + } + + /** + * Super class apply using template type. The default assigns config of the templated type, and also + * add listeners from subscriptions found on the base class ServiceConfig.listeners + */ + public T apply(T c) { + config = c; + addConfigListeners(c); + return config; + } - String yaml = CodecUtils.toYaml(inConfig); - ServiceConfig copyOfConfig = CodecUtils.fromYaml(yaml, inConfig.getClass()); - // TODO - handle subscriptions / listeners - if (copyOfConfig.listeners != null) { - for (Listener listener : copyOfConfig.listeners) { + /** + * The basic ServiceConfig has a list of listeners. These are definitions of + * other subscribers subscribing for data from this service. This method + * processes those listeners and adds them to the outbox notifyList. + */ + public ServiceConfig addConfigListeners(ServiceConfig config) { + if (config.listeners != null) { + for (Listener listener : config.listeners) { addListener(listener.method, listener.listener, listener.callback); } } - - this.config = copyOfConfig; return config; } /** - * Default getConfig returns name and type with null service specific config - * + * Default filtered config, used when saving, can be overriden by concrete class */ @Override - public ServiceConfig getConfig() { - + public ServiceConfig getFilteredConfig() { + // Make a copy, because we don't want to modify the original + ServiceConfig sc = CodecUtils.fromYaml(CodecUtils.toYaml(getConfig()), config.getClass()); Map> listeners = getOutbox().notifyList; List newListeners = new ArrayList<>(); @@ -1445,49 +1451,30 @@ public ServiceConfig getConfig() { for (MRLListener listener : list) { if (!listener.callbackName.endsWith("@webgui-client")) { // Removes the `@runtime-id` so configs still work with local IDs - // The StringUtils.removeEnd() call is a no-op when the ID is not our local ID, + // The StringUtils.removeEnd() call is a no-op when the ID is not our + // local ID, // so doesn't conflict with remote routes - Listener newConfigListener = new Listener( - listener.topicMethod, - CodecUtils.removeEnd( - listener.callbackName, - '@' + Platform.getLocalInstance().getId() - ), - listener.callbackMethod - ); + Listener newConfigListener = new Listener(listener.topicMethod, StringUtil.removeEnd(listener.callbackName, '@' + Platform.getLocalInstance().getId()), + listener.callbackMethod); newListeners.add(newConfigListener); } } } - if (newListeners.size() > 0) { - config.listeners = newListeners; + sc.listeners = newListeners; } - return config; - } - - // FIXME - NEED A BETTER SOLUTION !!! - @Override - public ServiceConfig getFilteredConfig() { - ServiceConfig sc = getConfig(); - // deep clone - sc = CodecUtils.fromYaml(CodecUtils.toYaml(sc), sc.getClass()); return sc; } - @Override - public void setConfig(ServiceConfig config) { - this.config = config; - } @Override public void setConfigValue(String fieldname, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { - log.info("setting field name fieldname {} to {}", fieldname, value); + log.info("setting field name fieldname {} to {}", fieldname, value); - Field field = config.getClass().getDeclaredField(fieldname); - // field.setAccessible(true); should not need this - it "should" be public - field.set(config, value); + Field field = getConfig().getClass().getDeclaredField(fieldname); + // field.setAccessible(true); should not need this - it "should" be public + field.set(getConfig(), value); } @Override @@ -1913,7 +1900,7 @@ synchronized public ServiceInterface startPeer(String peerKey) { peerKey = peerKey.trim(); // get current definition of config and peer - Peer peer = config.getPeer(peerKey); + Peer peer = getConfig().getPeer(peerKey); if (peer == null) { error("startPeer could not find peerKey of %s in %s", peerKey, getName()); @@ -1956,8 +1943,8 @@ synchronized public ServiceInterface startPeer(String peerKey) { */ synchronized public void releasePeer(String peerKey) { - if (config != null && config.getPeer(peerKey) != null) { - Peer peer = config.getPeer(peerKey); + if (getConfig() != null && getConfig().getPeer(peerKey) != null) { + Peer peer = getConfig().getPeer(peerKey); ServiceConfig sc = Runtime.getPlan().get(peer.name); // peer recursive if (sc != null && sc.getPeers() != null) { @@ -2732,10 +2719,10 @@ public int getCreationOrder() { */ public String getPeerName(String peerKey) { - if (config == null) { + if (getConfig() == null) { return null; } - return config.getPeerName(peerKey); + return getConfig().getPeerName(peerKey); } /** @@ -2778,7 +2765,7 @@ public void apply() { // updating plan Runtime.getPlan().put(getName(), sc); // applying config to self - apply(sc); + apply((T)sc); } /** @@ -2790,7 +2777,7 @@ public void apply() { * @param fullName */ public void setPeerName(String key, String fullName) { - Peer peer = config.getPeer(key); + Peer peer = getConfig().getPeer(key); String oldName = peer.name; peer.name = fullName; // update plan ? @@ -2822,7 +2809,7 @@ public void updatePeerType(String key, String peerType) { sc.putPeerType(key, String.format("%s.%s", getName(), key), peerType); } - Peer peer = config.getPeer(key); + Peer peer = getConfig().getPeer(key); peer.type = peerType; // not Needed diff --git a/src/main/java/org/myrobotlab/framework/Task.java b/src/main/java/org/myrobotlab/framework/Task.java index 68fdd29591..315b62a6f0 100644 --- a/src/main/java/org/myrobotlab/framework/Task.java +++ b/src/main/java/org/myrobotlab/framework/Task.java @@ -64,7 +64,7 @@ public void run() { Task t = new Task(this); // clear history list - becomes "new" message t.msg.historyList.clear(); - Timer timer = myService.tasks.get(taskName); + Timer timer = (Timer) myService.tasks.get(taskName); if (timer != null) { // timer = new Timer(String.format("%s.timer", getName())); try { diff --git a/src/main/java/org/myrobotlab/framework/interfaces/ConfigurableService.java b/src/main/java/org/myrobotlab/framework/interfaces/ConfigurableService.java new file mode 100644 index 0000000000..5e49ecd1fb --- /dev/null +++ b/src/main/java/org/myrobotlab/framework/interfaces/ConfigurableService.java @@ -0,0 +1,8 @@ +package org.myrobotlab.framework.interfaces; + +import org.myrobotlab.service.config.ServiceConfig; + +public interface ConfigurableService { + T getConfig(); + T apply(T c); +} diff --git a/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java b/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java index 1cc357418e..18334da95e 100644 --- a/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java +++ b/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java @@ -18,7 +18,7 @@ import org.slf4j.Logger; public interface ServiceInterface extends ServiceQueue, LoggingSink, NameTypeProvider, MessageSubscriber, MessageSender, StateSaver, Invoker, StatePublisher, StatusPublisher, - ServiceStatus, TaskManager, Attachable, MessageInvoker, Comparable { + ServiceStatus, TaskManager, Attachable, MessageInvoker, Comparable/*, ConfigurableService */{ // does this work ? Logger log = LoggerFactory.getLogger(Service.class); @@ -133,13 +133,6 @@ public interface ServiceInterface extends ServiceQueue, LoggingSink, NameTypePro */ ServiceConfig getConfig(); - /** - * sets config - just before apply - * - * @param config - */ - void setConfig(ServiceConfig config); - /** * reflectively sets a part of config * @@ -149,15 +142,6 @@ public interface ServiceInterface extends ServiceQueue, LoggingSink, NameTypePro void setConfigValue(String fieldname, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException; - /** - * Configure a service by merging in configuration - * - * @param config - * the config to load - * @return the loaded config. - */ - ServiceConfig apply(ServiceConfig config); - /** * Service life-cycle method, stops the inbox and outbox threads - typically * does not release "custom" resources. It's purpose primarily is to stop @@ -224,4 +208,13 @@ public interface ServiceInterface extends ServiceQueue, LoggingSink, NameTypePro */ ServiceConfig getFilteredConfig(); + /** + * Adds the subscribers specified in the Service.listener as listeners to + * this service. + * + * @param config + * @return + */ + public ServiceConfig addConfigListeners(ServiceConfig config); + } diff --git a/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java b/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java index 8fc649ea18..78da519eab 100644 --- a/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java +++ b/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java @@ -45,7 +45,7 @@ * https://learn.adafruit.com/16-channel-pwm-servo-driver */ @Ignore -public class Adafruit16CServoDriver extends Service implements I2CControl, ServoController, +public class Adafruit16CServoDriver extends Service implements I2CControl, ServoController, MotorController /* , ServoStatusPublisher */ { /** @@ -1024,8 +1024,8 @@ public String publishServoStopped(String name) { } @Override - public ServiceConfig getConfig() { - + public Adafruit16CServoDriverConfig getConfig() { + super.getConfig(); Adafruit16CServoDriverConfig config = (Adafruit16CServoDriverConfig)super.getConfig(); // FIXME remove member vars use config directly config.controller = controllerName; @@ -1035,8 +1035,8 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - Adafruit16CServoDriverConfig config = (Adafruit16CServoDriverConfig)super.apply(c); + public Adafruit16CServoDriverConfig apply(Adafruit16CServoDriverConfig c) { + super.apply(c); if (config.controller != null) { try { attach(config.controller); diff --git a/src/main/java/org/myrobotlab/service/AdafruitIna219.java b/src/main/java/org/myrobotlab/service/AdafruitIna219.java index 43c058d248..328c5e300b 100644 --- a/src/main/java/org/myrobotlab/service/AdafruitIna219.java +++ b/src/main/java/org/myrobotlab/service/AdafruitIna219.java @@ -12,6 +12,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; import org.myrobotlab.service.interfaces.VoltageSensorControl; @@ -25,7 +26,7 @@ * * References : https://www.adafruit.com/products/904 */ -public class AdafruitIna219 extends Service implements I2CControl, VoltageSensorControl { +public class AdafruitIna219 extends Service implements I2CControl, VoltageSensorControl { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java b/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java index 4d3c47c955..a089498d4f 100644 --- a/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java +++ b/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java @@ -19,6 +19,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractMotorController; +import org.myrobotlab.service.config.MotorConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; import org.myrobotlab.service.interfaces.MotorControl; @@ -33,7 +34,7 @@ * https://learn.adafruit.com/adafruit-dc-and-stepper-motor-hat-for-raspberry-pi/overview */ -public class AdafruitMotorHat4Pi extends AbstractMotorController implements I2CControl { +public class AdafruitMotorHat4Pi extends AbstractMotorController implements I2CControl { /** version of the library */ static public final String VERSION = "0.9"; diff --git a/src/main/java/org/myrobotlab/service/Ads1115.java b/src/main/java/org/myrobotlab/service/Ads1115.java index 429538e142..1594f287bd 100644 --- a/src/main/java/org/myrobotlab/service/Ads1115.java +++ b/src/main/java/org/myrobotlab/service/Ads1115.java @@ -66,7 +66,7 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -public class Ads1115 extends Service implements I2CControl, PinArrayControl { +public class Ads1115 extends Service implements I2CControl, PinArrayControl { /** * Publisher - Publishes pin data at a regular interval * @@ -1165,8 +1165,8 @@ public String getAddress() { } @Override - public ServiceConfig getConfig() { - Ads1115Config config = (Ads1115Config)super.getConfig(); + public Ads1115Config getConfig() { + super.getConfig(); // FIXME remove member variables - use config only config.bus = deviceBus; config.address = deviceAddress; @@ -1175,8 +1175,8 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - Ads1115Config config = (Ads1115Config) super.apply(c); + public Ads1115Config apply(Ads1115Config c) { + super.apply(c); deviceBus = config.bus; deviceAddress = config.address; if (config.controller != null) { diff --git a/src/main/java/org/myrobotlab/service/Amt203Encoder.java b/src/main/java/org/myrobotlab/service/Amt203Encoder.java index c4a22b9d0b..2c76aa21a1 100755 --- a/src/main/java/org/myrobotlab/service/Amt203Encoder.java +++ b/src/main/java/org/myrobotlab/service/Amt203Encoder.java @@ -2,6 +2,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractPinEncoder; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.EncoderControl; /** @@ -22,7 +23,7 @@ * @author kwatters * */ -public class Amt203Encoder extends AbstractPinEncoder implements EncoderControl { +public class Amt203Encoder extends AbstractPinEncoder implements EncoderControl { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Android.java b/src/main/java/org/myrobotlab/service/Android.java index 6ac23de8a3..3334088e75 100644 --- a/src/main/java/org/myrobotlab/service/Android.java +++ b/src/main/java/org/myrobotlab/service/Android.java @@ -5,9 +5,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Android extends Service { +public class Android extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Arduino.java b/src/main/java/org/myrobotlab/service/Arduino.java index 3568fcb598..89e407c18d 100644 --- a/src/main/java/org/myrobotlab/service/Arduino.java +++ b/src/main/java/org/myrobotlab/service/Arduino.java @@ -38,7 +38,6 @@ import org.myrobotlab.sensor.EncoderData; import org.myrobotlab.service.abstracts.AbstractMicrocontroller; import org.myrobotlab.service.config.ArduinoConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.DeviceMapping; import org.myrobotlab.service.data.PinData; import org.myrobotlab.service.data.SerialRelayData; @@ -71,7 +70,7 @@ import org.myrobotlab.service.interfaces.UltrasonicSensorController; import org.slf4j.Logger; -public class Arduino extends AbstractMicrocontroller implements I2CBusController, I2CController, SerialDataListener, ServoController, MotorController, NeoPixelController, +public class Arduino extends AbstractMicrocontroller implements I2CBusController, I2CController, SerialDataListener, ServoController, MotorController, NeoPixelController, UltrasonicSensorController, PortConnector, RecordControl, PortListener, PortPublisher, EncoderController, PinArrayPublisher, MrlCommPublisher, ServoStatusPublisher { transient public final static Logger log = LoggerFactory.getLogger(Arduino.class); @@ -2321,19 +2320,19 @@ public void neoPixelClear(String neopixel) { } @Override - public ServiceConfig getConfig() { - ArduinoConfig c = (ArduinoConfig) super.getConfig(); + public ArduinoConfig getConfig() { + super.getConfig(); // FIXME "port" shouldn't exist only config.port ! - c.port = port; - c.connect = isConnected(); + config.port = port; + config.connect = isConnected(); - return c; + return config; } @Override - public ServiceConfig apply(ServiceConfig c) { - ArduinoConfig config = (ArduinoConfig) super.apply(c); + public ArduinoConfig apply(ArduinoConfig c) { + super.apply(c); if (msg == null) { serial = (Serial) startPeer("serial"); diff --git a/src/main/java/org/myrobotlab/service/Arm.java b/src/main/java/org/myrobotlab/service/Arm.java index b86a279557..db695f01ef 100644 --- a/src/main/java/org/myrobotlab/service/Arm.java +++ b/src/main/java/org/myrobotlab/service/Arm.java @@ -28,13 +28,14 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** * Arm * */ -public class Arm extends Service { +public class Arm extends Service { public transient final static Logger log = LoggerFactory.getLogger(Arm.class.getCanonicalName()); diff --git a/src/main/java/org/myrobotlab/service/As5048AEncoder.java b/src/main/java/org/myrobotlab/service/As5048AEncoder.java index 34ac4ca212..f04b3d7289 100755 --- a/src/main/java/org/myrobotlab/service/As5048AEncoder.java +++ b/src/main/java/org/myrobotlab/service/As5048AEncoder.java @@ -2,6 +2,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractPinEncoder; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.EncoderControl; /** @@ -10,7 +11,7 @@ * @author kwatters * */ -public class As5048AEncoder extends AbstractPinEncoder implements EncoderControl { +public class As5048AEncoder extends AbstractPinEncoder implements EncoderControl { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/AudioCapture.java b/src/main/java/org/myrobotlab/service/AudioCapture.java index c1df1781fd..2ac2da72ee 100644 --- a/src/main/java/org/myrobotlab/service/AudioCapture.java +++ b/src/main/java/org/myrobotlab/service/AudioCapture.java @@ -50,13 +50,14 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** * AudioCapture - a service that can record and playback from a microphone. * */ -public class AudioCapture extends Service { +public class AudioCapture extends Service { public final static Logger log = LoggerFactory.getLogger(AudioCapture.class.getCanonicalName()); private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/AudioFile.java b/src/main/java/org/myrobotlab/service/AudioFile.java index b1ce0fe7e8..8b5a3cb828 100644 --- a/src/main/java/org/myrobotlab/service/AudioFile.java +++ b/src/main/java/org/myrobotlab/service/AudioFile.java @@ -62,7 +62,7 @@ * TODO - publishPeak interface * */ -public class AudioFile extends Service implements AudioPublisher, AudioControl { +public class AudioFile extends Service implements AudioPublisher, AudioControl { static final long serialVersionUID = 1L; static final Logger log = LoggerFactory.getLogger(AudioFile.class); @@ -519,7 +519,7 @@ public void stopPlaylist() { } @Override - public ServiceConfig getConfig() { + public AudioFileConfig getConfig() { AudioFileConfig c = (AudioFileConfig) super.getConfig(); // FIXME - remove members keep data in config ! @@ -537,9 +537,8 @@ public ServiceConfig getConfig() { return config; } - @Override - public ServiceConfig apply(ServiceConfig c) { - AudioFileConfig config = (AudioFileConfig) super.apply(c); + public AudioFileConfig apply(AudioFileConfig config) { + super.apply(config); setMute(config.mute); setTrack(config.currentTrack); setVolume(config.volume); @@ -554,11 +553,7 @@ public ServiceConfig apply(ServiceConfig c) { } } - // FIXME - THIS IS ALL THATS NEEDED AND IT CAN BE - // DONE IN THE SERVICE LEVEL - // if services need "special" handling they can override - this.config = c; - return c; + return config; } public double publishPeak(double peak) { diff --git a/src/main/java/org/myrobotlab/service/AzureTranslator.java b/src/main/java/org/myrobotlab/service/AzureTranslator.java index a4f5c08f39..d9c5aceb68 100644 --- a/src/main/java/org/myrobotlab/service/AzureTranslator.java +++ b/src/main/java/org/myrobotlab/service/AzureTranslator.java @@ -18,6 +18,7 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.TextListener; import org.myrobotlab.service.interfaces.TextPublisher; import org.myrobotlab.service.interfaces.Translator; @@ -31,7 +32,7 @@ import okhttp3.RequestBody; import okhttp3.Response; -public class AzureTranslator extends Service implements Translator, TextListener, TextPublisher { +public class AzureTranslator extends Service implements Translator, TextListener, TextPublisher { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/BeagleBoardBlack.java b/src/main/java/org/myrobotlab/service/BeagleBoardBlack.java index 49600b2b16..fd9513ea1f 100644 --- a/src/main/java/org/myrobotlab/service/BeagleBoardBlack.java +++ b/src/main/java/org/myrobotlab/service/BeagleBoardBlack.java @@ -5,6 +5,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -12,7 +13,7 @@ * service will allow access through Java to the GPIO of the BBB. Needs a Pi4J * code to be ported to a BBB4J library. */ -public class BeagleBoardBlack extends Service { +public class BeagleBoardBlack extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Blender.java b/src/main/java/org/myrobotlab/service/Blender.java index 18b8b57fda..9056dbef23 100644 --- a/src/main/java/org/myrobotlab/service/Blender.java +++ b/src/main/java/org/myrobotlab/service/Blender.java @@ -14,9 +14,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Blender extends Service { +public class Blender extends Service { /** * Control line - JSON over TCP/IP This is the single control communication diff --git a/src/main/java/org/myrobotlab/service/Blocks.java b/src/main/java/org/myrobotlab/service/Blocks.java index 49caf8bb28..c0362e2185 100644 --- a/src/main/java/org/myrobotlab/service/Blocks.java +++ b/src/main/java/org/myrobotlab/service/Blocks.java @@ -5,9 +5,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Blocks extends Service { +public class Blocks extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Bno055.java b/src/main/java/org/myrobotlab/service/Bno055.java index aab854397e..1086f749fc 100644 --- a/src/main/java/org/myrobotlab/service/Bno055.java +++ b/src/main/java/org/myrobotlab/service/Bno055.java @@ -12,6 +12,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.PinData; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; @@ -47,7 +48,7 @@ * DEALINGS IN THE SOFTWARE. =============================================== */ -public class Bno055 extends Service implements I2CControl, PinListener { +public class Bno055 extends Service implements I2CControl, PinListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/BoofCv.java b/src/main/java/org/myrobotlab/service/BoofCv.java index 21bdad5ee4..36b6d58239 100644 --- a/src/main/java/org/myrobotlab/service/BoofCv.java +++ b/src/main/java/org/myrobotlab/service/BoofCv.java @@ -6,11 +6,12 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.geometry.Point2df; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.Point2DfListener; import org.myrobotlab.service.interfaces.Point2DfPublisher; import org.slf4j.Logger; -public class BoofCv extends Service implements Point2DfPublisher, Point2DfListener { +public class BoofCv extends Service implements Point2DfPublisher, Point2DfListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Chassis.java b/src/main/java/org/myrobotlab/service/Chassis.java index c0b5da0549..6f2c5ba082 100644 --- a/src/main/java/org/myrobotlab/service/Chassis.java +++ b/src/main/java/org/myrobotlab/service/Chassis.java @@ -4,11 +4,12 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.MotorController; import org.slf4j.Logger; -public class Chassis extends Service { +public class Chassis extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/ChessGame.java b/src/main/java/org/myrobotlab/service/ChessGame.java index f9be32e27f..adb0c7dc22 100644 --- a/src/main/java/org/myrobotlab/service/ChessGame.java +++ b/src/main/java/org/myrobotlab/service/ChessGame.java @@ -31,9 +31,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class ChessGame extends Service { +public class ChessGame extends Service { public final static Logger log = LoggerFactory.getLogger(ChessGame.class.getCanonicalName()); diff --git a/src/main/java/org/myrobotlab/service/Clock.java b/src/main/java/org/myrobotlab/service/Clock.java index 49acdbc31a..df211bb4e3 100644 --- a/src/main/java/org/myrobotlab/service/Clock.java +++ b/src/main/java/org/myrobotlab/service/Clock.java @@ -14,7 +14,6 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.config.ClockConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -22,7 +21,7 @@ * generates a pulse with a timestamp on a regular interval defined by the * setInterval(Integer) method. Interval is in milliseconds. */ -public class Clock extends Service { +public class Clock extends Service { public class ClockThread implements Runnable { @@ -31,14 +30,13 @@ public class ClockThread implements Runnable { @Override public void run() { - ClockConfig c = (ClockConfig) config; try { - c.running = true; + config.running = true; invoke("publishClockStarted"); - while (c.running) { - Thread.sleep(c.interval); + while (config.running) { + Thread.sleep(config.interval); Date now = new Date(); for (Message msg : events) { send(msg); @@ -50,7 +48,7 @@ public void run() { } catch (InterruptedException e) { log.info("ClockThread interrupt"); } - c.running = false; + config.running = false; thread = null; } @@ -68,7 +66,6 @@ synchronized public void start() { } synchronized public void stop() { - ClockConfig c = (ClockConfig) config; if (thread != null) { thread.interrupt(); @@ -76,11 +73,11 @@ synchronized public void stop() { } else { log.info("{} already stopped", getName()); } - c.running = false; + config.running = false; Service.sleep(20); } } - + private static final long serialVersionUID = 1L; final public static Logger log = LoggerFactory.getLogger(Clock.class); @@ -159,8 +156,7 @@ public long publishEpoch(Date time) { * @param milliseconds */ public void setInterval(Integer milliseconds) { - ClockConfig c = (ClockConfig) config; - c.interval = milliseconds; + config.interval = milliseconds; broadcastState(); } @@ -181,8 +177,7 @@ public void startClock() { * @return */ public boolean isClockRunning() { - ClockConfig c = (ClockConfig) config; - return c.running; + return config.running; } /** @@ -203,12 +198,12 @@ public void stopService() { * @return */ public Integer getInterval() { - return ((ClockConfig) config).interval; + return config.interval; } - @Override - public ServiceConfig apply(ServiceConfig c) { - ClockConfig config = (ClockConfig) super.apply(c); + public ClockConfig apply(ClockConfig c) { + super.apply(c); + config = (ClockConfig)c; if (config.running != null) { if (config.running) { startClock(); @@ -233,7 +228,9 @@ public static void main(String[] args) throws Exception { // webgui.startService(); Clock c1 = (Clock) Runtime.start("c1", "Clock"); - c1.startClock(); + Runtime.setLogLevel("ERROR"); + // c1.startClock(); + Runtime.getInstance().connect("ws://localhost:8888"); boolean done = true; if (done) return; @@ -248,4 +245,6 @@ public static void main(String[] args) throws Exception { } } + + } \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/service/Cron.java b/src/main/java/org/myrobotlab/service/Cron.java index b59271beae..7e8e0ba00b 100644 --- a/src/main/java/org/myrobotlab/service/Cron.java +++ b/src/main/java/org/myrobotlab/service/Cron.java @@ -27,7 +27,7 @@ * can be any message that the service accepts. * */ -public class Cron extends Service { +public class Cron extends Service { public static class Task implements Serializable, Runnable { @@ -187,7 +187,9 @@ public String addTask(Task task) { } @Override - public ServiceConfig apply(ServiceConfig c) { + public CronConfig apply(CronConfig c) { + super.apply(config); + // deschedule current tasks removeAllTasks(); @@ -200,13 +202,13 @@ public ServiceConfig apply(ServiceConfig c) { } @Override - public ServiceConfig getConfig() { - CronConfig c = (CronConfig)config; - c.tasks = new ArrayList<>(); + public CronConfig getConfig() { + super.getConfig(); + config.tasks = new ArrayList<>(); for (Task task: tasks.values()) { - c.tasks.add(task); + config.tasks.add(task); } - return c; + return config; } public Map getCronTasks() { diff --git a/src/main/java/org/myrobotlab/service/Database.java b/src/main/java/org/myrobotlab/service/Database.java index ddcd521989..a90006dcd4 100644 --- a/src/main/java/org/myrobotlab/service/Database.java +++ b/src/main/java/org/myrobotlab/service/Database.java @@ -9,9 +9,10 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Database extends Service { +public class Database extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Deeplearning4j.java b/src/main/java/org/myrobotlab/service/Deeplearning4j.java index 0381ced24e..892e33cea6 100644 --- a/src/main/java/org/myrobotlab/service/Deeplearning4j.java +++ b/src/main/java/org/myrobotlab/service/Deeplearning4j.java @@ -85,6 +85,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.opencv.CloseableFrameConverter; import org.myrobotlab.opencv.YoloDetectedObject; +import org.myrobotlab.service.config.ServiceConfig; import org.nd4j.linalg.activations.Activation; import org.nd4j.linalg.api.ndarray.INDArray; import org.nd4j.linalg.dataset.api.iterator.DataSetIterator; @@ -112,7 +113,7 @@ * @author kwatters * */ -public class Deeplearning4j extends Service { +public class Deeplearning4j extends Service { private static final long serialVersionUID = 1L; transient public final static Logger log = LoggerFactory.getLogger(Deeplearning4j.class); diff --git a/src/main/java/org/myrobotlab/service/DiscordBot.java b/src/main/java/org/myrobotlab/service/DiscordBot.java index 19c9ec3171..3770877e84 100644 --- a/src/main/java/org/myrobotlab/service/DiscordBot.java +++ b/src/main/java/org/myrobotlab/service/DiscordBot.java @@ -29,7 +29,7 @@ * channels. The bot user also needs a token that is used to authenticate and * identify the bot. */ -public class DiscordBot extends Service implements UtterancePublisher, UtteranceListener, ImageListener { +public class DiscordBot extends Service implements UtterancePublisher, UtteranceListener, ImageListener { transient public final static Logger log = LoggerFactory.getLogger(DiscordBot.class); @@ -54,8 +54,8 @@ public DiscordBot(String reservedKey, String inId) { } @Override - public ServiceConfig apply(ServiceConfig c) { - DiscordBotConfig config = (DiscordBotConfig) super.apply(c); + public DiscordBotConfig apply(DiscordBotConfig c) { + super.apply(c); if (config.token != null) { setToken(config.token); @@ -103,20 +103,9 @@ public void attach(Attachable attachable) { } @Override - public ServiceConfig getConfig() { - // TODO: this is also an ugly pattern. you can't really call super get - // config here! - // ServiceConfig c = super.getConfig(); - // TODO: is this unsafe? - // TODO: what sets the type of this config? - /// TODO: this isn't good OO programming to have to do it this way. - DiscordBotConfig c = (DiscordBotConfig) super.getConfig(); - c.token = token; - - // REMOVED BECAUSE OVERLAP WITH SUBSCRIPTION -// Set listeners = getAttached("publishUtterance"); -// c.utteranceListeners = listeners.toArray(new String[listeners.size()]); - + public DiscordBotConfig getConfig() { + super.getConfig(); + config.token = token; return config; } diff --git a/src/main/java/org/myrobotlab/service/DiyServo.java b/src/main/java/org/myrobotlab/service/DiyServo.java index f69f501610..2e125d8509 100644 --- a/src/main/java/org/myrobotlab/service/DiyServo.java +++ b/src/main/java/org/myrobotlab/service/DiyServo.java @@ -38,6 +38,7 @@ import org.myrobotlab.math.MathUtils; import org.myrobotlab.math.interfaces.Mapper; import org.myrobotlab.service.abstracts.AbstractServo; +import org.myrobotlab.service.config.ServoConfig; import org.myrobotlab.service.data.PinData; import org.myrobotlab.service.interfaces.EncoderControl; import org.myrobotlab.service.interfaces.MotorControl; @@ -75,7 +76,7 @@ * TODO : move is not accurate ( 1° step seem not possible ) */ -public class DiyServo extends AbstractServo implements PinListener, ServiceLifeCycleListener { +public class DiyServo extends AbstractServo implements PinListener, ServiceLifeCycleListener { double lastOutput = 0.0; /** diff --git a/src/main/java/org/myrobotlab/service/Docker.java b/src/main/java/org/myrobotlab/service/Docker.java index d42947e4c7..4f36b0c456 100644 --- a/src/main/java/org/myrobotlab/service/Docker.java +++ b/src/main/java/org/myrobotlab/service/Docker.java @@ -6,19 +6,15 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.config.DockerConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import com.github.dockerjava.api.DockerClient; -import com.github.dockerjava.api.command.CreateContainerResponse; -import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.Container; -import com.github.dockerjava.api.model.PortBinding; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import com.github.dockerjava.core.DockerClientConfig; -public class Docker extends Service { +public class Docker extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index 2f1dc0de22..86dc854cf5 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -12,14 +12,13 @@ import org.myrobotlab.document.workflow.WorkflowMessage; import org.myrobotlab.document.workflow.WorkflowServer; import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.DocumentPipelineConfig; import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.DocumentListener; import org.myrobotlab.service.interfaces.DocumentPublisher; -public class DocumentPipeline extends Service implements DocumentListener, DocumentPublisher { +public class DocumentPipeline extends Service implements DocumentListener, DocumentPublisher { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/DruppNeck.java b/src/main/java/org/myrobotlab/service/DruppNeck.java index a4f608bd74..1c7aa42e39 100755 --- a/src/main/java/org/myrobotlab/service/DruppNeck.java +++ b/src/main/java/org/myrobotlab/service/DruppNeck.java @@ -4,6 +4,7 @@ import org.myrobotlab.kinematics.DruppIKSolver; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.MathUtils; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.ServoControl; /** @@ -17,7 +18,7 @@ * @author kwatters * */ -public class DruppNeck extends Service { +public class DruppNeck extends Service { private static final long serialVersionUID = 1L; // 3 servos for the drupp neck diff --git a/src/main/java/org/myrobotlab/service/EddieControlBoard.java b/src/main/java/org/myrobotlab/service/EddieControlBoard.java index 3ee28c11b9..7ffe8a822f 100644 --- a/src/main/java/org/myrobotlab/service/EddieControlBoard.java +++ b/src/main/java/org/myrobotlab/service/EddieControlBoard.java @@ -9,6 +9,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.MapperLinear; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.interfaces.JoystickListener; import org.myrobotlab.service.interfaces.KeyListener; @@ -21,7 +22,7 @@ * EddieControlBoard It can publish sensor data , control motors and more! * */ -public class EddieControlBoard extends Service implements KeyListener, SerialDataListener, JoystickListener { +public class EddieControlBoard extends Service implements KeyListener, SerialDataListener, JoystickListener { class SensorPoller extends Thread { diff --git a/src/main/java/org/myrobotlab/service/Elasticsearch.java b/src/main/java/org/myrobotlab/service/Elasticsearch.java index 52aa7c0d94..e3d5e41076 100644 --- a/src/main/java/org/myrobotlab/service/Elasticsearch.java +++ b/src/main/java/org/myrobotlab/service/Elasticsearch.java @@ -6,12 +6,13 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import pl.allegro.tech.embeddedelasticsearch.EmbeddedElastic; import pl.allegro.tech.embeddedelasticsearch.PopularProperties; -public class Elasticsearch extends Service { +public class Elasticsearch extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Email.java b/src/main/java/org/myrobotlab/service/Email.java index 904976f431..24a1414ed0 100644 --- a/src/main/java/org/myrobotlab/service/Email.java +++ b/src/main/java/org/myrobotlab/service/Email.java @@ -1,12 +1,9 @@ package org.myrobotlab.service; -import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.Level; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.logging.LoggingFactory; -import org.myrobotlab.service.config.EmailConfig; -import org.myrobotlab.service.data.ImageData; -import org.slf4j.Logger; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Properties; import javax.activation.DataHandler; import javax.activation.DataSource; @@ -19,10 +16,14 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Properties; + +import org.myrobotlab.framework.Service; +import org.myrobotlab.logging.Level; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.EmailConfig; +import org.myrobotlab.service.data.ImageData; +import org.slf4j.Logger; /** * @@ -34,7 +35,7 @@ * @author grog * */ -public class Email extends Service { +public class Email extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Emoji.java b/src/main/java/org/myrobotlab/service/Emoji.java index bb8ff1ee30..64930f2923 100644 --- a/src/main/java/org/myrobotlab/service/Emoji.java +++ b/src/main/java/org/myrobotlab/service/Emoji.java @@ -28,7 +28,7 @@ // emotionListener // Links // - http://googleemotionalindex.com/ -public class Emoji extends Service implements TextListener, EventHandler, ImagePublisher { +public class Emoji extends Service implements TextListener, EventHandler, ImagePublisher { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Esp8266.java b/src/main/java/org/myrobotlab/service/Esp8266.java index adaf5cb424..afdd32a9ae 100644 --- a/src/main/java/org/myrobotlab/service/Esp8266.java +++ b/src/main/java/org/myrobotlab/service/Esp8266.java @@ -5,9 +5,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Esp8266 extends Service { +public class Esp8266 extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Esp8266_01.java b/src/main/java/org/myrobotlab/service/Esp8266_01.java index 232ff6a8da..6630ea434f 100644 --- a/src/main/java/org/myrobotlab/service/Esp8266_01.java +++ b/src/main/java/org/myrobotlab/service/Esp8266_01.java @@ -20,6 +20,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; import org.slf4j.Logger; @@ -32,7 +33,7 @@ * */ // TODO Ensure that only one instance of RasPi can execute on each RaspBerry PI -public class Esp8266_01 extends Service implements I2CController { +public class Esp8266_01 extends Service implements I2CController { public static class I2CDeviceMap { public transient I2CControl control; diff --git a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java index 83f23d36cf..08d1ed45a4 100644 --- a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java +++ b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java @@ -32,7 +32,7 @@ * * @author GroG */ -public class FiniteStateMachine extends Service { +public class FiniteStateMachine extends Service { public final static Logger log = LoggerFactory.getLogger(FiniteStateMachine.class); @@ -227,17 +227,17 @@ public String publishNewState(String state) { } @Override - public ServiceConfig getConfig() { - FiniteStateMachineConfig c = (FiniteStateMachineConfig) super.getConfig(); - c.current = getCurrent(); - c.messageListeners = new ArrayList<>(); - c.messageListeners.addAll(messageListeners); - return c; + public FiniteStateMachineConfig getConfig() { + super.getConfig(); + config.current = getCurrent(); + config.messageListeners = new ArrayList<>(); + config.messageListeners.addAll(messageListeners); + return config; } @Override - public ServiceConfig apply(ServiceConfig c) { - FiniteStateMachineConfig config = (FiniteStateMachineConfig) super.apply(c); + public FiniteStateMachineConfig apply(FiniteStateMachineConfig c) { + super.apply(c); if (config.transitions != null) { diff --git a/src/main/java/org/myrobotlab/service/Git.java b/src/main/java/org/myrobotlab/service/Git.java index 5378f3f53c..1d2d3766f5 100644 --- a/src/main/java/org/myrobotlab/service/Git.java +++ b/src/main/java/org/myrobotlab/service/Git.java @@ -36,9 +36,10 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Git extends Service { +public class Git extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/GoPro.java b/src/main/java/org/myrobotlab/service/GoPro.java index 776230fdbe..c8fcd079fa 100644 --- a/src/main/java/org/myrobotlab/service/GoPro.java +++ b/src/main/java/org/myrobotlab/service/GoPro.java @@ -5,9 +5,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.GoProConfig; import org.slf4j.Logger; -public class GoPro extends Service { +public class GoPro extends Service { transient public HttpClient http; diff --git a/src/main/java/org/myrobotlab/service/GoogleCloud.java b/src/main/java/org/myrobotlab/service/GoogleCloud.java index 07ae425095..feb64b0999 100644 --- a/src/main/java/org/myrobotlab/service/GoogleCloud.java +++ b/src/main/java/org/myrobotlab/service/GoogleCloud.java @@ -8,6 +8,7 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import com.google.cloud.vision.v1.AnnotateImageRequest; @@ -40,7 +41,7 @@ //[END import_libraries] -public class GoogleCloud extends Service { +public class GoogleCloud extends Service { private static final long serialVersionUID = 1L; final static Logger log = LoggerFactory.getLogger(GoogleCloud.class); diff --git a/src/main/java/org/myrobotlab/service/GoogleSearch.java b/src/main/java/org/myrobotlab/service/GoogleSearch.java index f21ab6872a..4eceb24eed 100644 --- a/src/main/java/org/myrobotlab/service/GoogleSearch.java +++ b/src/main/java/org/myrobotlab/service/GoogleSearch.java @@ -20,7 +20,6 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.GoogleSearchConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.ImageData; import org.myrobotlab.service.data.Locale; import org.myrobotlab.service.data.SearchResults; @@ -31,7 +30,7 @@ import org.myrobotlab.service.interfaces.TextPublisher; import org.slf4j.Logger; -public class GoogleSearch extends Service implements ImagePublisher, TextPublisher, SearchPublisher, LocaleProvider { +public class GoogleSearch extends Service implements ImagePublisher, TextPublisher, SearchPublisher, LocaleProvider { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/GoogleTranslate.java b/src/main/java/org/myrobotlab/service/GoogleTranslate.java index 8e1899f8c0..2d322eabcf 100644 --- a/src/main/java/org/myrobotlab/service/GoogleTranslate.java +++ b/src/main/java/org/myrobotlab/service/GoogleTranslate.java @@ -8,6 +8,7 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import com.google.auth.oauth2.GoogleCredentials; @@ -15,7 +16,7 @@ import com.google.cloud.translate.TranslateOptions; import com.google.cloud.translate.Translation; -public class GoogleTranslate extends Service { +public class GoogleTranslate extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Gps.java b/src/main/java/org/myrobotlab/service/Gps.java index 704e675853..4dce48a0ca 100644 --- a/src/main/java/org/myrobotlab/service/Gps.java +++ b/src/main/java/org/myrobotlab/service/Gps.java @@ -13,6 +13,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.GpsConfig; import org.myrobotlab.service.interfaces.SerialDataListener; import org.myrobotlab.service.interfaces.SerialDevice; import org.slf4j.Logger; @@ -37,7 +38,7 @@ * wandered into our out of a fenced area. * */ -public class Gps extends Service implements SerialDataListener { +public class Gps extends Service implements SerialDataListener { /*********************************************************************************** * This block of methods will be used to GeoFencing This code is based on the diff --git a/src/main/java/org/myrobotlab/service/Gpt3.java b/src/main/java/org/myrobotlab/service/Gpt3.java index fbeae0a691..19da929f49 100644 --- a/src/main/java/org/myrobotlab/service/Gpt3.java +++ b/src/main/java/org/myrobotlab/service/Gpt3.java @@ -48,7 +48,7 @@ * @author GroG * */ -public class Gpt3 extends Service implements TextListener, TextPublisher, UtterancePublisher, UtteranceListener, ResponsePublisher { +public class Gpt3 extends Service implements TextListener, TextPublisher, UtterancePublisher, UtteranceListener, ResponsePublisher { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Hd44780.java b/src/main/java/org/myrobotlab/service/Hd44780.java index 2378ecfb28..43dfde676f 100644 --- a/src/main/java/org/myrobotlab/service/Hd44780.java +++ b/src/main/java/org/myrobotlab/service/Hd44780.java @@ -25,7 +25,7 @@ * @author Moz4r modified by Ray Edgley. * */ -public class Hd44780 extends Service implements TextListener { +public class Hd44780 extends Service implements TextListener { public final static Logger log = LoggerFactory.getLogger(Hd44780.class); @@ -720,8 +720,8 @@ public static void main(String[] args) { * Load the config into memory */ @Override - public ServiceConfig getConfig() { - Hd44780Config config = (Hd44780Config) super.getConfig(); + public Hd44780Config getConfig() { + super.getConfig(); if (pcfName != null) { config.controller = pcfName; } @@ -733,8 +733,8 @@ public ServiceConfig getConfig() { * Applies the config to the service attaching to the PCF8574 if it exists. */ @Override - public ServiceConfig apply(ServiceConfig c) { - Hd44780Config config = (Hd44780Config) super.apply(c); + public Hd44780Config apply(Hd44780Config c) { + super.apply(c); if (config.controller != null) { try { diff --git a/src/main/java/org/myrobotlab/service/HtmlFilter.java b/src/main/java/org/myrobotlab/service/HtmlFilter.java index ec3142f3a0..ff0a4b4fb1 100644 --- a/src/main/java/org/myrobotlab/service/HtmlFilter.java +++ b/src/main/java/org/myrobotlab/service/HtmlFilter.java @@ -21,7 +21,7 @@ * @author kwatters * */ -public class HtmlFilter extends Service implements TextListener, TextPublisher, TextFilter { +public class HtmlFilter extends Service implements TextListener, TextPublisher, TextFilter { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/HtmlParser.java b/src/main/java/org/myrobotlab/service/HtmlParser.java index f790e3235a..cb4ace1bba 100644 --- a/src/main/java/org/myrobotlab/service/HtmlParser.java +++ b/src/main/java/org/myrobotlab/service/HtmlParser.java @@ -5,9 +5,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class HtmlParser extends Service { +public class HtmlParser extends Service { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(HtmlParser.class); diff --git a/src/main/java/org/myrobotlab/service/HttpClient.java b/src/main/java/org/myrobotlab/service/HttpClient.java index 5da460bbf8..c7caaa2982 100644 --- a/src/main/java/org/myrobotlab/service/HttpClient.java +++ b/src/main/java/org/myrobotlab/service/HttpClient.java @@ -54,6 +54,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.InstallCert; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.HttpData; import org.myrobotlab.service.interfaces.HttpDataListener; import org.myrobotlab.service.interfaces.HttpResponseListener; @@ -73,7 +74,7 @@ * - Proxies proxies proxies ! - * https://memorynotfound.com/configure-http-proxy-settings-java/ */ -public class HttpClient extends Service implements TextPublisher { +public class HttpClient extends Service implements TextPublisher { public final static Logger log = LoggerFactory.getLogger(HttpClient.class); diff --git a/src/main/java/org/myrobotlab/service/I2cMux.java b/src/main/java/org/myrobotlab/service/I2cMux.java index cccb01b7c1..4f6c24b43e 100644 --- a/src/main/java/org/myrobotlab/service/I2cMux.java +++ b/src/main/java/org/myrobotlab/service/I2cMux.java @@ -31,7 +31,7 @@ * More Info : https://www.adafruit.com/product/2717 * */ -public class I2cMux extends Service implements I2CControl, I2CController { +public class I2cMux extends Service implements I2CControl, I2CController { private static final long serialVersionUID = 1L; @@ -362,7 +362,7 @@ public String getAddress() { } @Override - public ServiceConfig getConfig() { + public I2cMuxConfig getConfig() { I2cMuxConfig config = (I2cMuxConfig)super.getConfig(); // FIXME this should only be in config, no need for local fields config.bus = deviceBus; @@ -373,7 +373,7 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { + public I2cMuxConfig apply(I2cMuxConfig c) { I2cMuxConfig config = (I2cMuxConfig) super.apply(c); // FIXME - remove all this, it should "only" be in config deviceBus = config.bus; diff --git a/src/main/java/org/myrobotlab/service/IBus.java b/src/main/java/org/myrobotlab/service/IBus.java index e0c62fb281..0ea0d04b4e 100644 --- a/src/main/java/org/myrobotlab/service/IBus.java +++ b/src/main/java/org/myrobotlab/service/IBus.java @@ -4,6 +4,7 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.SerialDataListener; import org.myrobotlab.service.interfaces.SerialDevice; import org.slf4j.Logger; @@ -14,7 +15,7 @@ * @author aanon4 * */ -public class IBus extends Service implements SerialDataListener { +public class IBus extends Service implements SerialDataListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/ImageDisplay.java b/src/main/java/org/myrobotlab/service/ImageDisplay.java index e89f866566..ded65ab7b7 100644 --- a/src/main/java/org/myrobotlab/service/ImageDisplay.java +++ b/src/main/java/org/myrobotlab/service/ImageDisplay.java @@ -34,13 +34,12 @@ import org.myrobotlab.net.Http; import org.myrobotlab.service.config.ImageDisplayConfig; import org.myrobotlab.service.config.ImageDisplayConfig.Display; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.ImageData; import org.myrobotlab.service.interfaces.ImageListener; import org.myrobotlab.service.interfaces.ImagePublisher; import org.slf4j.Logger; -public class ImageDisplay extends Service implements ImageListener, MouseListener, ActionListener, MouseMotionListener { +public class ImageDisplay extends Service implements ImageListener, MouseListener, ActionListener, MouseMotionListener { final static Logger log = LoggerFactory.getLogger(ImageDisplay.class); @@ -80,8 +79,8 @@ public void actionPerformed(ActionEvent arg0) { } @Override - public ServiceConfig apply(ServiceConfig c) { - ImageDisplayConfig config = (ImageDisplayConfig) super.apply(c); + public ImageDisplayConfig apply(ImageDisplayConfig c) { + super.apply(c); if (config.displays != null) { for (String displayName : config.displays.keySet()) { close(displayName); diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index a1d220be7b..520077321f 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -44,7 +44,7 @@ import org.myrobotlab.service.interfaces.TextPublisher; import org.slf4j.Logger; -public class InMoov2 extends Service implements ServiceLifeCycleListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { +public class InMoov2 extends Service implements ServiceLifeCycleListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { public final static Logger log = LoggerFactory.getLogger(InMoov2.class); @@ -151,8 +151,8 @@ public void addTextListener(TextListener service) { } @Override - public ServiceConfig apply(ServiceConfig c) { - InMoov2Config config = (InMoov2Config) super.apply(c); + public InMoov2Config apply(InMoov2Config c) { + super.apply(c); try { locales = Locale.getLocaleMap("en-US", "fr-FR", "es-ES", "de-DE", "nl-NL", "ru-RU", "hi-IN", "it-IT", "fi-FI", "pt-PT", "tr-TR"); diff --git a/src/main/java/org/myrobotlab/service/InMoov2Arm.java b/src/main/java/org/myrobotlab/service/InMoov2Arm.java index c15f73bf49..a73f5527b5 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Arm.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Arm.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import org.myrobotlab.framework.Service; @@ -13,6 +12,7 @@ import org.myrobotlab.kinematics.DHRobotArm; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.math.MathUtils; +import org.myrobotlab.service.config.InMoov2ArmConfig; import org.myrobotlab.service.interfaces.IKJointAngleListener; import org.myrobotlab.service.interfaces.ServoControl; import org.slf4j.Logger; @@ -30,7 +30,7 @@ * startPeers() does * */ -public class InMoov2Arm extends Service implements IKJointAngleListener { +public class InMoov2Arm extends Service implements IKJointAngleListener { public final static Logger log = LoggerFactory.getLogger(InMoov2Arm.class); diff --git a/src/main/java/org/myrobotlab/service/InMoov2Hand.java b/src/main/java/org/myrobotlab/service/InMoov2Hand.java index 3c97ccb997..b1744ad03e 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Hand.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Hand.java @@ -13,6 +13,7 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.InMoov2HandConfig; import org.myrobotlab.service.data.LeapData; import org.myrobotlab.service.data.LeapHand; import org.myrobotlab.service.data.PinData; @@ -29,7 +30,7 @@ * * There is also leap motion support. */ -public class InMoov2Hand extends Service implements LeapDataListener, PinArrayListener { +public class InMoov2Hand extends Service implements LeapDataListener, PinArrayListener { public final static Logger log = LoggerFactory.getLogger(InMoov2Hand.class); diff --git a/src/main/java/org/myrobotlab/service/InMoov2Head.java b/src/main/java/org/myrobotlab/service/InMoov2Head.java index 7a7f9db801..be1a72f793 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Head.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Head.java @@ -3,7 +3,6 @@ import java.io.File; import java.io.IOException; import java.util.HashMap; -import java.util.Locale; import java.util.concurrent.ThreadLocalRandom; import org.myrobotlab.framework.Service; @@ -11,6 +10,7 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.InMoov2HeadConfig; import org.myrobotlab.service.interfaces.ServoControl; import org.slf4j.Logger; @@ -19,7 +19,7 @@ * servos for the following: jaw, eyeX, eyeY, rothead and neck. * */ -public class InMoov2Head extends Service { +public class InMoov2Head extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/InMoov2Torso.java b/src/main/java/org/myrobotlab/service/InMoov2Torso.java index d003e7f4c8..d2607a1033 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Torso.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Torso.java @@ -3,13 +3,13 @@ import java.io.File; import java.io.IOException; import java.util.HashMap; -import java.util.Locale; import org.myrobotlab.framework.Service; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.InMoov2TorsoConfig; import org.myrobotlab.service.interfaces.ServoControl; import org.slf4j.Logger; @@ -18,7 +18,7 @@ * midStom, and lowStom servos. * */ -public class InMoov2Torso extends Service { +public class InMoov2Torso extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/IndianTts.java b/src/main/java/org/myrobotlab/service/IndianTts.java index 636b668b36..0ca15d1cc6 100644 --- a/src/main/java/org/myrobotlab/service/IndianTts.java +++ b/src/main/java/org/myrobotlab/service/IndianTts.java @@ -8,6 +8,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; +import org.myrobotlab.service.config.IndianTtsConfig; import org.myrobotlab.service.data.AudioData; import org.myrobotlab.service.data.HttpData; import org.slf4j.Logger; @@ -18,7 +19,7 @@ * * http://indiantts.com/ */ -public class IndianTts extends AbstractSpeechSynthesis { +public class IndianTts extends AbstractSpeechSynthesis { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/IntegratedMovement.java b/src/main/java/org/myrobotlab/service/IntegratedMovement.java index bbe49028ea..cdbb59ef36 100644 --- a/src/main/java/org/myrobotlab/service/IntegratedMovement.java +++ b/src/main/java/org/myrobotlab/service/IntegratedMovement.java @@ -29,6 +29,7 @@ import org.myrobotlab.math.MathUtils; import org.myrobotlab.math.interfaces.Mapper; import org.myrobotlab.openni.OpenNiData; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.IKJointAnglePublisher; import org.myrobotlab.service.interfaces.ServoControl; import org.myrobotlab.service.interfaces.ServoEvent; @@ -51,7 +52,7 @@ * @author Christian/Calamity * */ -public class IntegratedMovement extends Service implements IKJointAnglePublisher { +public class IntegratedMovement extends Service implements IKJointAnglePublisher { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(IntegratedMovement.class); diff --git a/src/main/java/org/myrobotlab/service/Intro.java b/src/main/java/org/myrobotlab/service/Intro.java index 5569f275bd..ad64524e3c 100644 --- a/src/main/java/org/myrobotlab/service/Intro.java +++ b/src/main/java/org/myrobotlab/service/Intro.java @@ -13,9 +13,10 @@ import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Intro extends Service { +public class Intro extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/InverseKinematics.java b/src/main/java/org/myrobotlab/service/InverseKinematics.java index c9afaa6982..9c86b01004 100644 --- a/src/main/java/org/myrobotlab/service/InverseKinematics.java +++ b/src/main/java/org/myrobotlab/service/InverseKinematics.java @@ -5,6 +5,7 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -13,7 +14,7 @@ * will be replaced with the DH parameter based InverseKinematics3D service. * */ -public class InverseKinematics extends Service { +public class InverseKinematics extends Service { protected IKEngine ikEngine; diff --git a/src/main/java/org/myrobotlab/service/InverseKinematics3D.java b/src/main/java/org/myrobotlab/service/InverseKinematics3D.java index 8d2d2a7fca..05524a2630 100644 --- a/src/main/java/org/myrobotlab/service/InverseKinematics3D.java +++ b/src/main/java/org/myrobotlab/service/InverseKinematics3D.java @@ -14,6 +14,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.MathUtils; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.interfaces.IKJointAngleListener; import org.myrobotlab.service.interfaces.IKJointAnglePublisher; @@ -34,7 +35,7 @@ * @author kwatters * */ -public class InverseKinematics3D extends Service implements IKJointAnglePublisher, PointsListener { +public class InverseKinematics3D extends Service implements IKJointAnglePublisher, PointsListener { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(InverseKinematics3D.class); diff --git a/src/main/java/org/myrobotlab/service/IpCamera.java b/src/main/java/org/myrobotlab/service/IpCamera.java index 5a4c7592a0..295668ba62 100644 --- a/src/main/java/org/myrobotlab/service/IpCamera.java +++ b/src/main/java/org/myrobotlab/service/IpCamera.java @@ -14,6 +14,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -23,7 +24,7 @@ * Android bitmap to buffered image * Bitmap to BufferedImage - conversion once Bitmap class is serialized */ -public class IpCamera extends Service { +public class IpCamera extends Service { public class VideoProcess implements Runnable { @Override diff --git a/src/main/java/org/myrobotlab/service/JFugue.java b/src/main/java/org/myrobotlab/service/JFugue.java index ffdb99c0b1..f30468c6cb 100644 --- a/src/main/java/org/myrobotlab/service/JFugue.java +++ b/src/main/java/org/myrobotlab/service/JFugue.java @@ -31,6 +31,7 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -38,7 +39,7 @@ * some sounds and music based on string patterns that define the beat. * */ -public class JFugue extends Service { +public class JFugue extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java index 72a2f8ed0c..5f932f8853 100644 --- a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java +++ b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java @@ -124,7 +124,7 @@ * @author GroG, calamity, kwatters, moz4r and many others ... * */ -public class JMonkeyEngine extends Service implements Gateway, ActionListener, Simulator, EncoderListener, IKJointAngleListener, ServoStatusListener, ServoControlListener { +public class JMonkeyEngine extends Service implements Gateway, ActionListener, Simulator, EncoderListener, IKJointAngleListener, ServoStatusListener, ServoControlListener { final static String CAMERA = "camera"; @@ -2672,8 +2672,8 @@ public UserDataConfig toUserDataConfig(UserData userData) { } @Override - public ServiceConfig getConfig() { - JMonkeyEngineConfig config = (JMonkeyEngineConfig) super.getConfig(); + public JMonkeyEngineConfig getConfig() { + super.getConfig(); if (config.models != null) { Collections.sort(config.models); diff --git a/src/main/java/org/myrobotlab/service/JavaScript.java b/src/main/java/org/myrobotlab/service/JavaScript.java index 20816ae83e..533a43badc 100644 --- a/src/main/java/org/myrobotlab/service/JavaScript.java +++ b/src/main/java/org/myrobotlab/service/JavaScript.java @@ -9,9 +9,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class JavaScript extends Service { +public class JavaScript extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Joystick.java b/src/main/java/org/myrobotlab/service/Joystick.java index 414224bfec..9b7a1f8064 100644 --- a/src/main/java/org/myrobotlab/service/Joystick.java +++ b/src/main/java/org/myrobotlab/service/Joystick.java @@ -67,7 +67,7 @@ The buttons values (booleans) can be accessed individually, or * To Test java -Djava.library.path="./" -cp "./*" * net.java.games.input.test.ControllerReadTest */ -public class Joystick extends Service implements AnalogPublisher { +public class Joystick extends Service implements AnalogPublisher { public final static Logger log = LoggerFactory.getLogger(Joystick.class); private static final long serialVersionUID = 1L; @@ -695,8 +695,8 @@ public void moveTo(String axisName, double value) { } @Override - public ServiceConfig getConfig() { - JoystickConfig config = (JoystickConfig)super.getConfig(); + public JoystickConfig getConfig() { + super.getConfig(); config.controller = controller; if (analogListeners.size() > 0) { @@ -716,9 +716,10 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - + public JoystickConfig apply(JoystickConfig c) { + super.apply(c); // "special" needs native libs + // FIXME - should be done in startService initNativeLibs(); // scan for hardware controllers diff --git a/src/main/java/org/myrobotlab/service/KafkaConnector.java b/src/main/java/org/myrobotlab/service/KafkaConnector.java index d3dc3b7d3d..55c0687d74 100755 --- a/src/main/java/org/myrobotlab/service/KafkaConnector.java +++ b/src/main/java/org/myrobotlab/service/KafkaConnector.java @@ -7,6 +7,7 @@ import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.myrobotlab.framework.Service; +import org.myrobotlab.service.config.ServiceConfig; /** * A kafka connector that can subscribe to a string/string kafka stopic and @@ -19,7 +20,7 @@ * @author kwatters * */ -public class KafkaConnector extends Service { +public class KafkaConnector extends Service { public String bootstrapServers = "localhost:9092"; public String groupId = "test"; diff --git a/src/main/java/org/myrobotlab/service/Keyboard.java b/src/main/java/org/myrobotlab/service/Keyboard.java index 4d76c8d028..9b53cfbe39 100644 --- a/src/main/java/org/myrobotlab/service/Keyboard.java +++ b/src/main/java/org/myrobotlab/service/Keyboard.java @@ -14,6 +14,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.geometry.Point2df; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -22,7 +23,7 @@ * * */ -public class Keyboard extends Service { +public class Keyboard extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/KeyboardSim.java b/src/main/java/org/myrobotlab/service/KeyboardSim.java index f2ea917eeb..b9052a0541 100644 --- a/src/main/java/org/myrobotlab/service/KeyboardSim.java +++ b/src/main/java/org/myrobotlab/service/KeyboardSim.java @@ -8,13 +8,14 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** * @author MaVo (MyRobotLab) / LunDev (GitHub) */ -public class KeyboardSim extends Service { +public class KeyboardSim extends Service { static final long serialVersionUID = 1L; static final Logger log = LoggerFactory.getLogger(KeyboardSim.class); diff --git a/src/main/java/org/myrobotlab/service/LeapMotion.java b/src/main/java/org/myrobotlab/service/LeapMotion.java index b63cc1b904..ed1387d4b3 100644 --- a/src/main/java/org/myrobotlab/service/LeapMotion.java +++ b/src/main/java/org/myrobotlab/service/LeapMotion.java @@ -26,7 +26,7 @@ import com.leapmotion.leap.Hand; import com.leapmotion.leap.Vector; -public class LeapMotion extends Service implements LeapDataListener, LeapDataPublisher, PointPublisher { +public class LeapMotion extends Service implements LeapDataListener, LeapDataPublisher, PointPublisher { private static final long serialVersionUID = 1L; @@ -192,7 +192,6 @@ private double computeAngleDegrees(Finger f, Vector palmNormal) { } private LeapHand mapLeapHandData(Hand lh) { - LeapMotionConfig c = (LeapMotionConfig) config; LeapHand mrlHand = new LeapHand(); // process the normal Vector palmNormal = lh.palmNormal(); @@ -368,19 +367,19 @@ public void stop() { MapperLinear rightThumb = new MapperLinear(); @Override - public ServiceConfig apply(ServiceConfig config) { - LeapMotionConfig c = (LeapMotionConfig) super.apply(config); - leftIndex = new MapperLinear(c.leftIndex.minIn, c.leftIndex.maxIn, c.leftIndex.minOut, c.leftIndex.maxOut); - leftMiddle = new MapperLinear(c.leftMiddle.minIn, c.leftMiddle.maxIn, c.leftMiddle.minOut, c.leftMiddle.maxOut); - leftRing = new MapperLinear(c.leftRing.minIn, c.leftRing.maxIn, c.leftRing.minOut, c.leftRing.maxOut); - leftPinky = new MapperLinear(c.leftPinky.minIn, c.leftPinky.maxIn, c.leftPinky.minOut, c.leftPinky.maxOut); - leftThumb = new MapperLinear(c.leftThumb.minIn, c.leftThumb.maxIn, c.leftThumb.minOut, c.leftThumb.maxOut); - - rightIndex = new MapperLinear(c.rightIndex.minIn, c.rightIndex.maxIn, c.rightIndex.minOut, c.rightIndex.maxOut); - rightMiddle = new MapperLinear(c.rightMiddle.minIn, c.rightMiddle.maxIn, c.rightMiddle.minOut, c.rightMiddle.maxOut); - rightRing = new MapperLinear(c.rightRing.minIn, c.rightRing.maxIn, c.rightRing.minOut, c.rightRing.maxOut); - rightPinky = new MapperLinear(c.rightPinky.minIn, c.rightPinky.maxIn, c.rightPinky.minOut, c.rightPinky.maxOut); - rightThumb = new MapperLinear(c.rightThumb.minIn, c.rightThumb.maxIn, c.rightThumb.minOut, c.rightThumb.maxOut); + public LeapMotionConfig apply(LeapMotionConfig config) { + super.apply(config); + leftIndex = new MapperLinear(config.leftIndex.minIn, config.leftIndex.maxIn, config.leftIndex.minOut, config.leftIndex.maxOut); + leftMiddle = new MapperLinear(config.leftMiddle.minIn, config.leftMiddle.maxIn, config.leftMiddle.minOut, config.leftMiddle.maxOut); + leftRing = new MapperLinear(config.leftRing.minIn, config.leftRing.maxIn, config.leftRing.minOut, config.leftRing.maxOut); + leftPinky = new MapperLinear(config.leftPinky.minIn, config.leftPinky.maxIn, config.leftPinky.minOut, config.leftPinky.maxOut); + leftThumb = new MapperLinear(config.leftThumb.minIn, config.leftThumb.maxIn, config.leftThumb.minOut, config.leftThumb.maxOut); + + rightIndex = new MapperLinear(config.rightIndex.minIn, config.rightIndex.maxIn, config.rightIndex.minOut, config.rightIndex.maxOut); + rightMiddle = new MapperLinear(config.rightMiddle.minIn, config.rightMiddle.maxIn, config.rightMiddle.minOut, config.rightMiddle.maxOut); + rightRing = new MapperLinear(config.rightRing.minIn, config.rightRing.maxIn, config.rightRing.minOut, config.rightRing.maxOut); + rightPinky = new MapperLinear(config.rightPinky.minIn, config.rightPinky.maxIn, config.rightPinky.minOut, config.rightPinky.maxOut); + rightThumb = new MapperLinear(config.rightThumb.minIn, config.rightThumb.maxIn, config.rightThumb.minOut, config.rightThumb.maxOut); return config; } diff --git a/src/main/java/org/myrobotlab/service/LeapMotion2.java b/src/main/java/org/myrobotlab/service/LeapMotion2.java index e918f95b54..a48a1c7cbd 100644 --- a/src/main/java/org/myrobotlab/service/LeapMotion2.java +++ b/src/main/java/org/myrobotlab/service/LeapMotion2.java @@ -20,7 +20,7 @@ import okhttp3.Response; import okhttp3.WebSocket; -public class LeapMotion2 extends Service implements LeapDataListener, LeapDataPublisher, PointPublisher, RemoteMessageHandler { +public class LeapMotion2 extends Service implements LeapDataListener, LeapDataPublisher, PointPublisher, RemoteMessageHandler { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Lidar.java b/src/main/java/org/myrobotlab/service/Lidar.java index fb76b5b29f..db3447ace7 100644 --- a/src/main/java/org/myrobotlab/service/Lidar.java +++ b/src/main/java/org/myrobotlab/service/Lidar.java @@ -9,10 +9,11 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.LidarConfig; import org.myrobotlab.service.interfaces.SerialDataListener; import org.slf4j.Logger; -public class Lidar extends Service implements SerialDataListener { +public class Lidar extends Service implements SerialDataListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/LidarVlp16.java b/src/main/java/org/myrobotlab/service/LidarVlp16.java index 6cbc349957..52b0531901 100644 --- a/src/main/java/org/myrobotlab/service/LidarVlp16.java +++ b/src/main/java/org/myrobotlab/service/LidarVlp16.java @@ -12,9 +12,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.LidarConfig; import org.slf4j.Logger; -public class LidarVlp16 extends Service { +public class LidarVlp16 extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Lloyd.java b/src/main/java/org/myrobotlab/service/Lloyd.java index 0ea682cc68..ca1dc561ff 100755 --- a/src/main/java/org/myrobotlab/service/Lloyd.java +++ b/src/main/java/org/myrobotlab/service/Lloyd.java @@ -24,6 +24,7 @@ import org.myrobotlab.opencv.OpenCVFilterDL4JTransfer; import org.myrobotlab.opencv.OpenCVFilterLloyd; import org.myrobotlab.programab.OOBPayload; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.SpeechRecognizer; import org.myrobotlab.service.interfaces.SpeechSynthesis; import org.nd4j.linalg.dataset.api.iterator.DataSetIterator; @@ -38,7 +39,7 @@ * @author kwatters * */ -public class Lloyd extends Service { +public class Lloyd extends Service { public final static Logger log = LoggerFactory.getLogger(Lloyd.class); private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Lm75a.java b/src/main/java/org/myrobotlab/service/Lm75a.java index d0029a9942..d0c05fc7dd 100644 --- a/src/main/java/org/myrobotlab/service/Lm75a.java +++ b/src/main/java/org/myrobotlab/service/Lm75a.java @@ -12,6 +12,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; import org.slf4j.Logger; @@ -23,7 +24,7 @@ * * References : https://www.nxp.com/documents/data_sheet/LM75A.pdf */ -public class Lm75a extends Service implements I2CControl { +public class Lm75a extends Service implements I2CControl { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/LocalSpeech.java b/src/main/java/org/myrobotlab/service/LocalSpeech.java index ee95463632..9496bf3fb7 100644 --- a/src/main/java/org/myrobotlab/service/LocalSpeech.java +++ b/src/main/java/org/myrobotlab/service/LocalSpeech.java @@ -18,7 +18,6 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.LocalSpeechConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.AudioData; import org.myrobotlab.service.data.Locale; import org.slf4j.Logger; @@ -53,7 +52,7 @@ * https://github.com/espeak-ng/espeak-ng/blob/master/docs/mbrola.md#linux-installation * */ -public class LocalSpeech extends AbstractSpeechSynthesis { +public class LocalSpeech extends AbstractSpeechSynthesis { public final static Logger log = LoggerFactory.getLogger(LocalSpeech.class); @@ -479,13 +478,12 @@ public void setTtsPath(String ttsPath) { this.ttsPath = ttsPath; } - @Override - public ServiceConfig apply(ServiceConfig config) { - LocalSpeechConfig c = (LocalSpeechConfig) super.apply(config); + public LocalSpeechConfig apply(LocalSpeechConfig config) { + super.apply(config); // setup the default tts per os Platform platform = Runtime.getPlatform(); - if (c.speechType == null) { + if (config.speechType == null) { if (platform.isWindows()) { setTts(); } else if (platform.isMac()) { @@ -496,13 +494,13 @@ public ServiceConfig apply(ServiceConfig config) { error("%s unknown platform %s", getName(), platform.getOS()); } } else { - setSpeechType(c.speechType); + setSpeechType(config.speechType); } - if (c.voice != null) { - setVoice(c.voice); + if (config.voice != null) { + setVoice(config.voice); } - return c; + return config; } public static void main(String[] args) { diff --git a/src/main/java/org/myrobotlab/service/Log.java b/src/main/java/org/myrobotlab/service/Log.java index 1e8508e487..fa88732014 100644 --- a/src/main/java/org/myrobotlab/service/Log.java +++ b/src/main/java/org/myrobotlab/service/Log.java @@ -34,6 +34,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -43,7 +44,7 @@ import ch.qos.logback.core.spi.FilterReply; import ch.qos.logback.core.status.Status; -public class Log extends Service implements Appender { +public class Log extends Service implements Appender { public static class LogEntry { public long ts; diff --git a/src/main/java/org/myrobotlab/service/Mail.java b/src/main/java/org/myrobotlab/service/Mail.java index 5b8d0f47ed..3a0e482a42 100644 --- a/src/main/java/org/myrobotlab/service/Mail.java +++ b/src/main/java/org/myrobotlab/service/Mail.java @@ -15,6 +15,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -27,7 +28,7 @@ * -gmail-smtp-example/ * */ -public class Mail extends Service { +public class Mail extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/MarySpeech.java b/src/main/java/org/myrobotlab/service/MarySpeech.java index 1fb17de3cb..f1686253b1 100644 --- a/src/main/java/org/myrobotlab/service/MarySpeech.java +++ b/src/main/java/org/myrobotlab/service/MarySpeech.java @@ -15,6 +15,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; +import org.myrobotlab.service.config.MarySpeechConfig; import org.myrobotlab.service.data.AudioData; import org.slf4j.Logger; import org.xml.sax.SAXException; @@ -35,7 +36,7 @@ * More info at : http://mary.dfki.de/ * */ -public class MarySpeech extends AbstractSpeechSynthesis { +public class MarySpeech extends AbstractSpeechSynthesis { public final static Logger log = LoggerFactory.getLogger(MarySpeech.class); diff --git a/src/main/java/org/myrobotlab/service/Maven.java b/src/main/java/org/myrobotlab/service/Maven.java index 6b100cdf6e..c9afdb4a52 100644 --- a/src/main/java/org/myrobotlab/service/Maven.java +++ b/src/main/java/org/myrobotlab/service/Maven.java @@ -12,11 +12,12 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import picocli.CommandLine.Option; -public class Maven extends Service { +public class Maven extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/MobilePlatform.java b/src/main/java/org/myrobotlab/service/MobilePlatform.java index 14809daeaa..60d4e36fdd 100644 --- a/src/main/java/org/myrobotlab/service/MobilePlatform.java +++ b/src/main/java/org/myrobotlab/service/MobilePlatform.java @@ -30,6 +30,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.slf4j.Logger; @@ -52,7 +53,7 @@ * Differential_steer_drive_dead_reckoning */ @Deprecated // use Chassis -public class MobilePlatform extends Service { +public class MobilePlatform extends Service { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(MobilePlatform.class); diff --git a/src/main/java/org/myrobotlab/service/Motor.java b/src/main/java/org/myrobotlab/service/Motor.java index f57df11ab7..6b410e1f7d 100644 --- a/src/main/java/org/myrobotlab/service/Motor.java +++ b/src/main/java/org/myrobotlab/service/Motor.java @@ -13,7 +13,7 @@ * */ -public class Motor extends AbstractMotor { +public class Motor extends AbstractMotor { private static final long serialVersionUID = 1L; @@ -70,7 +70,7 @@ public void setPwmFreq(Integer pwmfreq) { } @Override - public ServiceConfig getConfig() { + public MotorConfig getConfig() { MotorConfig config = (MotorConfig)super.getConfig(); config.dirPin = getDirPin(); config.pwrPin = getPwrPin(); @@ -78,9 +78,8 @@ public ServiceConfig getConfig() { return config; } - @Override - public ServiceConfig apply(ServiceConfig c) { - MotorConfig config = (MotorConfig)super.apply(c); + public MotorConfig apply(MotorConfig config) { + super.apply(config); if (config.pwrPin != null) { setPwrPin(pwrPin); @@ -91,7 +90,7 @@ public ServiceConfig apply(ServiceConfig c) { if (config.pwmFreq != null) { setPwmFreq(pwmFreq); } - return c; + return config; } public static void main(String[] args) { diff --git a/src/main/java/org/myrobotlab/service/MotorDualPwm.java b/src/main/java/org/myrobotlab/service/MotorDualPwm.java index 9a6b872d71..10687ba938 100644 --- a/src/main/java/org/myrobotlab/service/MotorDualPwm.java +++ b/src/main/java/org/myrobotlab/service/MotorDualPwm.java @@ -10,7 +10,7 @@ import org.myrobotlab.service.config.MotorDualPwmConfig; import org.myrobotlab.service.config.ServiceConfig; -public class MotorDualPwm extends AbstractMotor { +public class MotorDualPwm extends AbstractMotor { private static final long serialVersionUID = 1L; protected String leftPwmPin; @@ -69,18 +69,17 @@ public void setPwmFreq(Integer pwmfreq) { } @Override - public ServiceConfig getConfig() { + public MotorDualPwmConfig getConfig() { // FIXME - may need to do call super.config for config that has parent :( - MotorDualPwmConfig config = (MotorDualPwmConfig)super.getConfig(); + super.getConfig(); config.leftPwmPin = leftPwmPin; config.rightPwmPin = rightPwmPin; config.pwmFreq = pwmFreq; return config; } - @Override - public ServiceConfig apply(ServiceConfig c) { - MotorDualPwmConfig config = (MotorDualPwmConfig)super.apply(c); + public MotorDualPwmConfig apply(MotorDualPwmConfig c) { + super.apply(c); if (config.leftPwmPin != null) { setLeftPwmPin(config.leftPwmPin); } @@ -90,7 +89,7 @@ public ServiceConfig apply(ServiceConfig c) { if (config.pwmFreq != null) { setPwmFreq(config.pwmFreq); } - return c; + return config; } public static void main(String[] args) throws InterruptedException { diff --git a/src/main/java/org/myrobotlab/service/MotorHat4Pi.java b/src/main/java/org/myrobotlab/service/MotorHat4Pi.java index 927d2a98e8..6862aaf807 100644 --- a/src/main/java/org/myrobotlab/service/MotorHat4Pi.java +++ b/src/main/java/org/myrobotlab/service/MotorHat4Pi.java @@ -8,9 +8,8 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractMotor; import org.myrobotlab.service.config.MotorHat4PiConfig; -import org.myrobotlab.service.config.ServiceConfig; -public class MotorHat4Pi extends AbstractMotor { +public class MotorHat4Pi extends AbstractMotor { private static final long serialVersionUID = 1L; Integer leftDirPin; @@ -92,16 +91,14 @@ public String getMotorId() { } @Override - public ServiceConfig getConfig() { + public MotorHat4PiConfig getConfig() { // FIXME - may need to do call super.config for config that has parent :( - MotorHat4PiConfig config = (MotorHat4PiConfig) super.getConfig(); config.motorId = motorId; return config; } - @Override - public ServiceConfig apply(ServiceConfig c) { - MotorHat4PiConfig config = (MotorHat4PiConfig) super.apply(c); + public MotorHat4PiConfig apply(MotorHat4PiConfig c) { + super.apply(c); setMotor(config.motorId); return c; } diff --git a/src/main/java/org/myrobotlab/service/MotorPort.java b/src/main/java/org/myrobotlab/service/MotorPort.java index 00abe31899..d908d79e37 100644 --- a/src/main/java/org/myrobotlab/service/MotorPort.java +++ b/src/main/java/org/myrobotlab/service/MotorPort.java @@ -15,7 +15,7 @@ * string value can handle either we use a String port. * */ -public class MotorPort extends AbstractMotor { +public class MotorPort extends AbstractMotor { private static final long serialVersionUID = 1L; public MotorPort(String n, String id) { diff --git a/src/main/java/org/myrobotlab/service/MouseSim.java b/src/main/java/org/myrobotlab/service/MouseSim.java index 33f67420c8..050aa2c7c9 100644 --- a/src/main/java/org/myrobotlab/service/MouseSim.java +++ b/src/main/java/org/myrobotlab/service/MouseSim.java @@ -8,13 +8,14 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** * @author MaVo (MyRobotLab) / LunDev (GitHub) */ -public class MouseSim extends Service { +public class MouseSim extends Service { static final long serialVersionUID = 1L; static final Logger log = LoggerFactory.getLogger(MouseSim.class); diff --git a/src/main/java/org/myrobotlab/service/MouthControl.java b/src/main/java/org/myrobotlab/service/MouthControl.java index 5653c97a62..f89ac8ce3e 100644 --- a/src/main/java/org/myrobotlab/service/MouthControl.java +++ b/src/main/java/org/myrobotlab/service/MouthControl.java @@ -19,7 +19,7 @@ * It's peers are the jaw servo, speech service and an arduino. * */ -public class MouthControl extends Service implements SpeechListener { +public class MouthControl extends Service implements SpeechListener { private static final long serialVersionUID = 1L; @@ -229,9 +229,9 @@ public void setmouth(Integer closed, Integer opened) { } @Override - public ServiceConfig getConfig() { + public MouthControlConfig getConfig() { - MouthControlConfig config = (MouthControlConfig)super.getConfig(); + super.getConfig(); // FIXME - remove local fields, use config only config.jaw = jaw; config.mouth = mouth; @@ -246,8 +246,8 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - MouthControlConfig config = (MouthControlConfig) super.apply(c); + public MouthControlConfig apply(MouthControlConfig c) { + super.apply(c); // FIXME - remove local fields, use config only mouthClosedPos = config.mouthClosedPos; mouthOpenedPos = config.mouthOpenedPos; diff --git a/src/main/java/org/myrobotlab/service/Mpr121.java b/src/main/java/org/myrobotlab/service/Mpr121.java index e0d4bced9c..b3a11ec802 100644 --- a/src/main/java/org/myrobotlab/service/Mpr121.java +++ b/src/main/java/org/myrobotlab/service/Mpr121.java @@ -33,7 +33,7 @@ * https://www.sparkfun.com/datasheets/Components/MPR121.pdf * */ -public class Mpr121 extends Service implements I2CControl, PinArrayControl { +public class Mpr121 extends Service implements I2CControl, PinArrayControl { /** * Publisher - Publishes pin data at a regular interval */ @@ -926,14 +926,8 @@ public void setDeviceAddress(String deviceAddress) { } @Override - public ServiceConfig getConfig() { - Mpr121Config config = (Mpr121Config) super.getConfig(); - return config; - } - - @Override - public ServiceConfig apply(ServiceConfig c) { - Mpr121Config config = (Mpr121Config) super.apply(c); + public Mpr121Config apply(Mpr121Config c) { + super.apply(c); // FIXME remove local fields in favor of config only if (config.address != null) { setAddress(config.address); diff --git a/src/main/java/org/myrobotlab/service/Mpu6050.java b/src/main/java/org/myrobotlab/service/Mpu6050.java index cd4d6956b4..ac33ec9907 100644 --- a/src/main/java/org/myrobotlab/service/Mpu6050.java +++ b/src/main/java/org/myrobotlab/service/Mpu6050.java @@ -39,7 +39,7 @@ * */ -public class Mpu6050 extends Service implements I2CControl, OrientationPublisher { +public class Mpu6050 extends Service implements I2CControl, OrientationPublisher { private static final long serialVersionUID = 1L; @@ -4435,8 +4435,8 @@ public boolean isAttached(Attachable instance) { } @Override - public ServiceConfig getConfig() { - Mpu6050Config config = (Mpu6050Config)super.getConfig(); + public Mpu6050Config getConfig() { + super.getConfig(); // FIXME remove local fields in favor or config config.start = publisher.isRunning; config.sampleRate = sampleRateHz; @@ -4447,7 +4447,7 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { + public Mpu6050Config apply(Mpu6050Config c) { Mpu6050Config config = (Mpu6050Config) super.apply(c); // FIXME remove local fields in favor or config if (config.start) { diff --git a/src/main/java/org/myrobotlab/service/Mqtt.java b/src/main/java/org/myrobotlab/service/Mqtt.java index ddb0a5990b..8466016039 100644 --- a/src/main/java/org/myrobotlab/service/Mqtt.java +++ b/src/main/java/org/myrobotlab/service/Mqtt.java @@ -32,6 +32,7 @@ import org.myrobotlab.mqtt.MqttMsg; import org.myrobotlab.net.Connection; import org.myrobotlab.net.SslUtil; +import org.myrobotlab.service.config.MqttConfig; import org.myrobotlab.service.interfaces.Gateway; import org.myrobotlab.service.interfaces.KeyConsumer; import org.slf4j.Logger; @@ -69,7 +70,7 @@ * @author kmcgerald and GroG * */ -public class Mqtt extends Service implements MqttCallback, IMqttActionListener, Gateway, KeyConsumer { +public class Mqtt extends Service implements MqttCallback, IMqttActionListener, Gateway, KeyConsumer { public final static Logger log = LoggerFactory.getLogger(Mqtt.class); diff --git a/src/main/java/org/myrobotlab/service/MqttBroker.java b/src/main/java/org/myrobotlab/service/MqttBroker.java index bbee5bfb81..2a80bd9edd 100644 --- a/src/main/java/org/myrobotlab/service/MqttBroker.java +++ b/src/main/java/org/myrobotlab/service/MqttBroker.java @@ -60,7 +60,7 @@ * whole array is decoded - then each parameter is, similar to http form values * */ -public class MqttBroker extends Service implements InterceptHandler, Gateway, KeyConsumer { +public class MqttBroker extends Service implements InterceptHandler, Gateway, KeyConsumer { public final static Logger log = LoggerFactory.getLogger(MqttBroker.class); @@ -673,20 +673,20 @@ public void setKey(String keyName, String keyValue) { } @Override - public ServiceConfig getConfig() { - MqttBrokerConfig c = (MqttBrokerConfig)super.getConfig(); + public MqttBrokerConfig getConfig() { + super.getConfig(); // FIXME - remove local fields in favor of just config - c.address = address; - c.mqttPort = mqttPort; - c.wsPort = wsPort; - c.username = username; - c.password = password; - return c; + config.address = address; + config.mqttPort = mqttPort; + config.wsPort = wsPort; + config.username = username; + config.password = password; + return config; } @Override - public ServiceConfig apply(ServiceConfig c) { - MqttBrokerConfig config = (MqttBrokerConfig) super.apply(c); + public MqttBrokerConfig apply(MqttBrokerConfig c) { + super.apply(c); // FIXME - remove local fields in favor of just config address = config.address; mqttPort = config.mqttPort; diff --git a/src/main/java/org/myrobotlab/service/MultiWii.java b/src/main/java/org/myrobotlab/service/MultiWii.java index 457015cfd5..d91cdb8d58 100644 --- a/src/main/java/org/myrobotlab/service/MultiWii.java +++ b/src/main/java/org/myrobotlab/service/MultiWii.java @@ -5,6 +5,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.SerialDevice; import org.slf4j.Logger; @@ -16,7 +17,7 @@ * MultiWii is a general purpose software to control a multirotor RC model. * http://www.multiwii.com/ */ -public class MultiWii extends Service { +public class MultiWii extends Service { transient public SerialDevice serial; diff --git a/src/main/java/org/myrobotlab/service/MyoThalmic.java b/src/main/java/org/myrobotlab/service/MyoThalmic.java index 496442ccf7..f658c9e1bf 100644 --- a/src/main/java/org/myrobotlab/service/MyoThalmic.java +++ b/src/main/java/org/myrobotlab/service/MyoThalmic.java @@ -5,6 +5,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.MyoData; import org.myrobotlab.service.interfaces.MyoDataListener; import org.myrobotlab.service.interfaces.MyoDataPublisher; @@ -44,7 +45,7 @@ * https://github.com/NicholasAStuart/myo-java-JNI-Library * */ -public class MyoThalmic extends Service implements DeviceListener, MyoDataListener, MyoDataPublisher { +public class MyoThalmic extends Service implements DeviceListener, MyoDataListener, MyoDataPublisher { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/NeoPixel.java b/src/main/java/org/myrobotlab/service/NeoPixel.java index 47fa43eff2..e13daf273d 100644 --- a/src/main/java/org/myrobotlab/service/NeoPixel.java +++ b/src/main/java/org/myrobotlab/service/NeoPixel.java @@ -44,7 +44,7 @@ import org.myrobotlab.service.interfaces.NeoPixelController; import org.slf4j.Logger; -public class NeoPixel extends Service implements NeoPixelControl { +public class NeoPixel extends Service implements NeoPixelControl { /** * Thread to do animations Java side and push the changing of pixels to the diff --git a/src/main/java/org/myrobotlab/service/OakD.java b/src/main/java/org/myrobotlab/service/OakD.java index 902cf34bbd..99e9fdbfea 100644 --- a/src/main/java/org/myrobotlab/service/OakD.java +++ b/src/main/java/org/myrobotlab/service/OakD.java @@ -5,7 +5,6 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.ServiceConfig; -import org.myrobotlab.service.config.ServoConfig; import org.slf4j.Logger; /** * @@ -15,7 +14,7 @@ * @author GroG * */ -public class OakD extends Service { +public class OakD extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/OculusDiy.java b/src/main/java/org/myrobotlab/service/OculusDiy.java index a4097da3ab..a848cb2989 100644 --- a/src/main/java/org/myrobotlab/service/OculusDiy.java +++ b/src/main/java/org/myrobotlab/service/OculusDiy.java @@ -7,6 +7,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.MapperLinear; +import org.myrobotlab.service.config.OculusDiyConfig; import org.myrobotlab.service.data.Orientation; import org.myrobotlab.service.interfaces.OrientationListener; import org.myrobotlab.service.interfaces.PinArrayControl; @@ -18,7 +19,7 @@ * build of MRLComm to work. Check with \@Alessandruino for questions. * */ -public class OculusDiy extends Service implements OrientationListener { +public class OculusDiy extends Service implements OrientationListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/OculusRift.java b/src/main/java/org/myrobotlab/service/OculusRift.java index 7bea8f6e63..fadc3f2bef 100644 --- a/src/main/java/org/myrobotlab/service/OculusRift.java +++ b/src/main/java/org/myrobotlab/service/OculusRift.java @@ -13,6 +13,7 @@ import org.myrobotlab.opencv.OpenCVFilterTranspose; import org.myrobotlab.opencv.OpenCVFilterUndistort; import org.myrobotlab.opencv.OpenCVFilterYolo; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.Orientation; import org.myrobotlab.service.interfaces.PointPublisher; import org.slf4j.Logger; @@ -36,7 +37,7 @@ * */ // TODO: implement publishOculusRiftData ... -public class OculusRift extends Service implements PointPublisher { +public class OculusRift extends Service implements PointPublisher { public static final int ABS_TIME_MS = 0; public static final boolean LATENCY_MARKER = false; diff --git a/src/main/java/org/myrobotlab/service/OledSsd1306.java b/src/main/java/org/myrobotlab/service/OledSsd1306.java index aed9aa7b4d..eb75bbeca4 100644 --- a/src/main/java/org/myrobotlab/service/OledSsd1306.java +++ b/src/main/java/org/myrobotlab/service/OledSsd1306.java @@ -19,6 +19,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; import org.slf4j.Logger; @@ -49,7 +50,7 @@ * pin and VCC * */ -public class OledSsd1306 extends Service implements I2CControl { +public class OledSsd1306 extends Service implements I2CControl { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index b18858bad4..c73b4413c3 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -125,7 +125,6 @@ import org.myrobotlab.reflection.Reflector; import org.myrobotlab.service.abstracts.AbstractComputerVision; import org.myrobotlab.service.config.OpenCVConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.ImageData; import org.myrobotlab.service.interfaces.ImageListener; import org.myrobotlab.service.interfaces.ImagePublisher; @@ -144,7 +143,7 @@ * Audet : https://github.com/bytedeco/javacv * */ -public class OpenCV extends AbstractComputerVision implements ImagePublisher { +public class OpenCV extends AbstractComputerVision implements ImagePublisher { int vpId = 0; @@ -2023,8 +2022,8 @@ public void saveFile(String filename, String data) { } @Override - public ServiceConfig getConfig() { - OpenCVConfig config = (OpenCVConfig) super.getConfig(); + public OpenCVConfig getConfig() { + super.getConfig(); // FIXME - remove member vars use config only config.capturing = capturing; config.cameraIndex = cameraIndex; @@ -2041,8 +2040,8 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - OpenCVConfig config = (OpenCVConfig) super.apply(c); + public OpenCVConfig apply(OpenCVConfig c) { + super.apply(c); setCameraIndex(config.cameraIndex); setGrabberType(config.grabberType); setInputFileName(config.inputFile); diff --git a/src/main/java/org/myrobotlab/service/OpenNi.java b/src/main/java/org/myrobotlab/service/OpenNi.java index ed7f0f65a1..fc3d278cf9 100644 --- a/src/main/java/org/myrobotlab/service/OpenNi.java +++ b/src/main/java/org/myrobotlab/service/OpenNi.java @@ -19,6 +19,7 @@ import org.myrobotlab.openni.PImage; import org.myrobotlab.openni.PVector; import org.myrobotlab.openni.Skeleton; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.VideoSink; import org.slf4j.Logger; @@ -48,7 +49,7 @@ * * */ -public class OpenNi extends Service // implements +public class OpenNi extends Service // implements // UserTracker.NewFrameListener, // HandTracker.NewFrameListener { diff --git a/src/main/java/org/myrobotlab/service/OpenWeatherMap.java b/src/main/java/org/myrobotlab/service/OpenWeatherMap.java index c2a494502a..47227141f1 100644 --- a/src/main/java/org/myrobotlab/service/OpenWeatherMap.java +++ b/src/main/java/org/myrobotlab/service/OpenWeatherMap.java @@ -7,7 +7,6 @@ import org.json.JSONObject; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.config.OpenWeatherMapConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -18,7 +17,7 @@ * ( 3 hours TO 5 days forecast ) * */ -public class OpenWeatherMap extends HttpClient { + public class OpenWeatherMap extends HttpClient { private static final long serialVersionUID = 1L; private String apiForecast = "http://api.openweathermap.org/data/2.5/forecast/?q="; @@ -252,8 +251,8 @@ public String getApiKey() { } @Override - public ServiceConfig getConfig() { - OpenWeatherMapConfig config = (OpenWeatherMapConfig)super.getConfig(); + public OpenWeatherMapConfig getConfig() { + super.getConfig(); // FIXME - remove local fields in favor of only config config.currentUnits = units; config.currentTown = location; @@ -261,8 +260,8 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - OpenWeatherMapConfig config = (OpenWeatherMapConfig) super.apply(c); + public OpenWeatherMapConfig apply(OpenWeatherMapConfig c) { + super.apply(c); // FIXME - remove local fields in favor of only config if (config.currentUnits != null) { setUnits(config.currentUnits); diff --git a/src/main/java/org/myrobotlab/service/Osc.java b/src/main/java/org/myrobotlab/service/Osc.java index 63bfe69efd..5f4dc3e929 100644 --- a/src/main/java/org/myrobotlab/service/Osc.java +++ b/src/main/java/org/myrobotlab/service/Osc.java @@ -14,6 +14,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import com.illposed.osc.OSCListener; @@ -21,7 +22,7 @@ import com.illposed.osc.OSCPortIn; import com.illposed.osc.OSCPortOut; -public class Osc extends Service implements OSCListener { +public class Osc extends Service implements OSCListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Pcf8574.java b/src/main/java/org/myrobotlab/service/Pcf8574.java index 8723ffa480..be2682cbba 100644 --- a/src/main/java/org/myrobotlab/service/Pcf8574.java +++ b/src/main/java/org/myrobotlab/service/Pcf8574.java @@ -45,7 +45,7 @@ * */ -public class Pcf8574 extends Service +public class Pcf8574 extends Service implements I2CControl, /* FIXME - add I2CController */ PinArrayControl { /** * Publisher - Publishes pin data at a regular interval @@ -726,14 +726,14 @@ public void stopService() { } @Override - public ServiceConfig getConfig() { - Pcf8574Config config = (Pcf8574Config) super.getConfig(); + public Pcf8574Config getConfig() { + super.getConfig(); return config; } @Override - public ServiceConfig apply(ServiceConfig c) { - Pcf8574Config config = (Pcf8574Config) super.apply(c); + public Pcf8574Config apply(Pcf8574Config c) { + super.apply(c); // FIXME remove local fields in favor of config only if (config.address != null) { setAddress(config.address); diff --git a/src/main/java/org/myrobotlab/service/Pid.java b/src/main/java/org/myrobotlab/service/Pid.java index b8894435f8..d2a630eb2c 100644 --- a/src/main/java/org/myrobotlab/service/Pid.java +++ b/src/main/java/org/myrobotlab/service/Pid.java @@ -62,7 +62,7 @@ * https://en.wikipedia.org/wiki/PID_controller#Integral_windup * */ -public class Pid extends Service implements PidControl { +public class Pid extends Service implements PidControl { public static class PidOutput { public long ts; @@ -519,8 +519,8 @@ private double constrain(double value, Double min, Double max) { } @Override - public ServiceConfig getConfig() { - PidConfig config = (PidConfig)super.getConfig(); + public PidConfig getConfig() { + super.getConfig(); config.data = data; return config; } @@ -533,8 +533,8 @@ public void reset(String key) { } @Override - public ServiceConfig apply(ServiceConfig c) { - PidConfig config = (PidConfig) super.apply(c); + public PidConfig apply(PidConfig c) { + super.apply(c); if (config.data != null) { data = config.data; for (String key : config.data.keySet()) { diff --git a/src/main/java/org/myrobotlab/service/Pingdar.java b/src/main/java/org/myrobotlab/service/Pingdar.java index 62c6d6f290..fe931b945c 100644 --- a/src/main/java/org/myrobotlab/service/Pingdar.java +++ b/src/main/java/org/myrobotlab/service/Pingdar.java @@ -6,6 +6,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.sensor.EncoderData; import org.myrobotlab.sensor.EncoderListener; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.RangeListener; import org.myrobotlab.service.interfaces.RangingControl; import org.slf4j.Logger; @@ -16,7 +17,7 @@ * module. The result is a sonar style range finding. * */ -public class Pingdar extends Service implements RangingControl, RangeListener, EncoderListener { +public class Pingdar extends Service implements RangingControl, RangeListener, EncoderListener { public static class Point { diff --git a/src/main/java/org/myrobotlab/service/Pir.java b/src/main/java/org/myrobotlab/service/Pir.java index bc5f35328c..5a2f0d750c 100644 --- a/src/main/java/org/myrobotlab/service/Pir.java +++ b/src/main/java/org/myrobotlab/service/Pir.java @@ -16,7 +16,7 @@ import org.myrobotlab.service.interfaces.PinListener; import org.slf4j.Logger; -public class Pir extends Service implements PinListener { +public class Pir extends Service implements PinListener { public final static Logger log = LoggerFactory.getLogger(Pir.class); @@ -216,8 +216,8 @@ public boolean isEnabled() { } @Override - public ServiceConfig apply(ServiceConfig c) { - PirConfig config = (PirConfig) super.apply(c); + public PirConfig apply(PirConfig c) { + super.apply(c); if (config.controller != null) { attach(config.controller);; @@ -233,16 +233,16 @@ public ServiceConfig apply(ServiceConfig c) { } @Override - public ServiceConfig getConfig() { - PirConfig c = (PirConfig)super.getConfig(); - if (c.controller != null) { + public PirConfig getConfig() { + super.getConfig(); + if (config.controller != null) { // it makes sense that the controller should always be local for a PIR // but in general this is bad practice on 2 levels // 1. in some other context it might make sense not to be local // 2. it should just be another listener on ServiceConfig.listener - c.controller=CodecUtils.getShortName(c.controller); + config.controller=CodecUtils.getShortName(config.controller); } - return c; + return config; } @Override diff --git a/src/main/java/org/myrobotlab/service/Polly.java b/src/main/java/org/myrobotlab/service/Polly.java index e2acb2c260..5a447902d4 100644 --- a/src/main/java/org/myrobotlab/service/Polly.java +++ b/src/main/java/org/myrobotlab/service/Polly.java @@ -45,7 +45,7 @@ * @author GroG * */ -public class Polly extends AbstractSpeechSynthesis { +public class Polly extends AbstractSpeechSynthesis { private static final long serialVersionUID = 1L; @@ -253,8 +253,7 @@ public boolean isReady() { return ready; } - @Override - public ServiceConfig apply(ServiceConfig c) { + public PollyConfig apply(PollyConfig c) { super.apply(c); getVoices(); return c; diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index ff123320fc..63aebc9030 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -61,7 +61,7 @@ * @author kwatters * */ -public class ProgramAB extends Service +public class ProgramAB extends Service implements TextListener, TextPublisher, LocaleProvider, LogPublisher, ProgramABListener, UtterancePublisher, UtteranceListener, ResponsePublisher { /** @@ -559,8 +559,7 @@ public void reloadSession(String userName, String botName) throws IOException { * @return */ public Map getPredicates() { - ProgramABConfig c = (ProgramABConfig) config; - return getPredicates(c.currentUserName, c.currentBotName); + return getPredicates(config.currentUserName, config.currentBotName); } /** @@ -638,8 +637,7 @@ public void removeBotProperty(String botName, String name) { } public Session startSession() throws IOException { - ProgramABConfig c = (ProgramABConfig) config; - return startSession(c.currentUserName); + return startSession(config.currentUserName); } // FIXME - it should just set the current userName only @@ -811,15 +809,13 @@ public String setPath(String path) { } public void setCurrentBotName(String botName) { - ProgramABConfig c = (ProgramABConfig) config; - c.currentBotName = botName; + config.currentBotName = botName; invoke("getBotImage", botName); broadcastState(); } public void setCurrentUserName(String currentUserName) { - ProgramABConfig c = (ProgramABConfig) config; - c.currentUserName = currentUserName; + config.currentUserName = currentUserName; broadcastState(); } @@ -832,13 +828,11 @@ public String getSessionKey(String userName, String botName) { } public String getCurrentUserName() { - ProgramABConfig c = (ProgramABConfig) config; - return c.currentUserName; + return config.currentUserName; } public String getCurrentBotName() { - ProgramABConfig c = (ProgramABConfig) config; - return c.currentBotName; + return config.currentBotName; } /** @@ -998,8 +992,7 @@ public String publishLog(String msg) { } public BotInfo getBotInfo() { - ProgramABConfig c = (ProgramABConfig) config; - return getBotInfo(c.currentBotName); + return getBotInfo(config.currentBotName); } /** @@ -1078,54 +1071,54 @@ public void saveAimlFile(String botName, String filename, String data) { } @Override - public ServiceConfig getConfig() { - ProgramABConfig c = (ProgramABConfig) super.getConfig(); - if (c.bots == null) { - c.bots = new ArrayList<>(); + public ProgramABConfig getConfig() { + super.getConfig(); + if (config.bots == null) { + config.bots = new ArrayList<>(); } - c.bots.clear(); + config.bots.clear(); for (BotInfo bot : bots.values()) { Path pathAbsolute = Paths.get(bot.path.getAbsolutePath()); Path pathBase = Paths.get(System.getProperty("user.dir")); Path pathRelative = pathBase.relativize(pathAbsolute); - c.bots.add(pathRelative.toString()); + config.bots.add(pathRelative.toString()); } - return c; + return config; } @Override - public ServiceConfig apply(ServiceConfig config) { - ProgramABConfig c = (ProgramABConfig) super.apply(config); - if (c.bots != null && c.bots.size() > 0) { + public ProgramABConfig apply(ProgramABConfig c) { + super.apply(c); + if (config.bots != null && config.bots.size() > 0) { // bots.clear(); - for (String botPath : c.bots) { + for (String botPath : config.bots) { addBotPath(botPath); } } - if (c.botDir == null) { - c.botDir = getResourceDir(); + if (config.botDir == null) { + config.botDir = getResourceDir(); } - List botsFromScanning = scanForBots(c.botDir); + List botsFromScanning = scanForBots(config.botDir); for (File file : botsFromScanning) { addBotPath(file.getAbsolutePath()); } - if (c.currentUserName != null) { - setCurrentUserName(c.currentUserName); + if (config.currentUserName != null) { + setCurrentUserName(config.currentUserName); } - if (c.currentBotName != null) { - setCurrentUserName(c.currentBotName); + if (config.currentBotName != null) { + setCurrentUserName(config.currentBotName); } - if (c.startTopic != null) { - setTopic(c.startTopic); + if (config.startTopic != null) { + setTopic(config.startTopic); } @@ -1297,23 +1290,19 @@ synchronized public void addCategoryToFile(Bot bot, Category c) { * wakes the global session up */ public void wake() { - ProgramABConfig c = (ProgramABConfig) super.getConfig(); - c.sleep = false; + config.sleep = false; } /** * sleeps the global session */ public void sleep() { - ProgramABConfig c = (ProgramABConfig) super.getConfig(); - c.sleep = true; + config.sleep = true; } @Override public void onUtterance(Utterance utterance) throws Exception { - ProgramABConfig c = (ProgramABConfig) super.getConfig(); - log.info("Utterance Received " + utterance); boolean talkToBots = false; @@ -1343,8 +1332,8 @@ public void onUtterance(Utterance utterance) throws Exception { // TODO: don't talk to bots.. it won't go well.. // TODO: the discord api can provide use the list of mentioned users. // for now.. we'll just see if we see Mr. Turing as a substring. - c.sleep = (c.sleep || utterance.text.contains("@")) && !utterance.text.contains(botName); - if (!c.sleep) { + config.sleep = (config.sleep || utterance.text.contains("@")) && !utterance.text.contains(botName); + if (!config.sleep) { shouldIRespond = true; } } diff --git a/src/main/java/org/myrobotlab/service/Py4j.java b/src/main/java/org/myrobotlab/service/Py4j.java index 89002d8ee6..fd2755a733 100644 --- a/src/main/java/org/myrobotlab/service/Py4j.java +++ b/src/main/java/org/myrobotlab/service/Py4j.java @@ -3,7 +3,6 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; @@ -54,7 +53,7 @@ * * @author GroG */ -public class Py4j extends Service implements GatewayServerListener { +public class Py4j extends Service implements GatewayServerListener { /** * POJO class to tie all the data elements of a external python process diff --git a/src/main/java/org/myrobotlab/service/Python.java b/src/main/java/org/myrobotlab/service/Python.java index 1021af0d5c..02ce853f15 100644 --- a/src/main/java/org/myrobotlab/service/Python.java +++ b/src/main/java/org/myrobotlab/service/Python.java @@ -50,7 +50,7 @@ * @author GroG * */ -public class Python extends Service implements ServiceLifeCycleListener, MessageListener { +public class Python extends Service implements ServiceLifeCycleListener, MessageListener { /** * this thread handles all callbacks to Python process all input and sets msg @@ -177,7 +177,7 @@ public void run() { } } } - + public final static transient Logger log = LoggerFactory.getLogger(Python.class); // TODO this needs to be moved into an actual cache if it is to be used // Cache of compile python code @@ -313,8 +313,7 @@ public Python(String n, String id) { * @throws IOException */ public void openScript(String scriptName) throws IOException { - PythonConfig c = (PythonConfig)config; - File script = new File(c.scriptRootDir + fs + scriptName); + File script = new File(config.scriptRootDir + fs + scriptName); if (!script.exists()) { error("file %s not found", script.getAbsolutePath()); @@ -327,7 +326,6 @@ public void openScript(String scriptName) throws IOException { public void closeScript(String file) { - PythonConfig c = (PythonConfig) config; if (openedScripts.containsKey(file)) { openedScripts.remove(file); broadcastState(); @@ -408,9 +406,8 @@ synchronized public void createPythonInterpreter() { } public void addModulePath(String path) { - PythonConfig c = (PythonConfig) config; - if (c.modulePaths != null) { - c.modulePaths.add(path); + if (config.modulePaths != null) { + config.modulePaths.add(path); } if (interp != null) { @@ -751,8 +748,7 @@ public void setLocalScriptDir(String path) { * @throws IOException */ public void saveScript(String scriptName, String code) throws IOException { - PythonConfig c = (PythonConfig)config; - FileIO.toFile(c.scriptRootDir + fs + scriptName, code); + FileIO.toFile(config.scriptRootDir + fs + scriptName, code); info("saved file %s", scriptName); } @@ -791,11 +787,10 @@ public void defaultInvokeMethod(String method, Object... params) { synchronized public void startService() { super.startService(); - PythonConfig c = (PythonConfig) config; - if (c.scriptRootDir == null) { - c.scriptRootDir = new File(getDataInstanceDir()).getAbsolutePath(); + if (config.scriptRootDir == null) { + config.scriptRootDir = new File(getDataInstanceDir()).getAbsolutePath(); } - File dataDir = new File(c.scriptRootDir); + File dataDir = new File(config.scriptRootDir); dataDir.mkdirs(); Map services = Runtime.getLocalServices(); @@ -806,8 +801,8 @@ synchronized public void startService() { Runtime.getInstance().attachServiceLifeCycleListener(getName()); // run start scripts if there are any - if (c.startScripts != null) { - for (String script : c.startScripts) { + if (config.startScripts != null) { + for (String script : config.startScripts) { // i think in this context its safer to block try { execFile(script, true); @@ -856,9 +851,7 @@ public boolean stop() { @Override public void stopService() { // run any stop scripts - PythonConfig c = (PythonConfig) config; - - for (String script : c.stopScripts) { + for (String script : config.stopScripts) { // i think in this context its safer to block try { execFile(script, true); @@ -949,9 +942,9 @@ public void onStopped(String fullname) { } - @Override - public ServiceConfig apply(ServiceConfig c) { - PythonConfig config = (PythonConfig) super.apply(c); + public PythonConfig apply(PythonConfig c) { + super.apply(c); + config = (PythonConfig)c; // apply is the first method called after construction, // since we offer the capability of executing scripts specified in config @@ -991,12 +984,11 @@ public ServiceConfig apply(ServiceConfig c) { * @throws IOException */ public List getScriptList() throws IOException { - PythonConfig c = (PythonConfig)config; List sorted = new ArrayList<>(); - List files = FileIO.getFileList(c.scriptRootDir, true); + List files = FileIO.getFileList(config.scriptRootDir, true); for (File file : files) { if (file.toString().endsWith(".py")) { - sorted.add(file.toString().substring(c.scriptRootDir.length() + 1)); + sorted.add(file.toString().substring(config.scriptRootDir.length() + 1)); } } Collections.sort(sorted); @@ -1067,4 +1059,9 @@ public static void main(String[] args) { } + @Override + public PythonConfig getConfig() { + return config; + } + } diff --git a/src/main/java/org/myrobotlab/service/Random.java b/src/main/java/org/myrobotlab/service/Random.java index faed6d4364..4e5b368b10 100644 --- a/src/main/java/org/myrobotlab/service/Random.java +++ b/src/main/java/org/myrobotlab/service/Random.java @@ -18,7 +18,6 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.RandomConfig; import org.myrobotlab.service.config.RandomConfig.RandomMessageConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; /** @@ -27,7 +26,7 @@ * @author GroG * */ -public class Random extends Service { +public class Random extends Service { private static final long serialVersionUID = 1L; @@ -236,9 +235,8 @@ public void process(String key) { } @Override - public ServiceConfig getConfig() { - - RandomConfig config = (RandomConfig)super.getConfig(); + public RandomConfig getConfig() { + super.getConfig(); config.enabled = enabled; @@ -256,8 +254,8 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - RandomConfig config = (RandomConfig) c; + public RandomConfig apply(RandomConfig c) { + super.apply(c); enabled = config.enabled; try { @@ -272,7 +270,7 @@ public ServiceConfig apply(ServiceConfig c) { error(e); } - return c; + return config; } public RandomMessage remove(String name, String method) { diff --git a/src/main/java/org/myrobotlab/service/RasPi.java b/src/main/java/org/myrobotlab/service/RasPi.java index 5cdc18f0d8..ea6e33444f 100644 --- a/src/main/java/org/myrobotlab/service/RasPi.java +++ b/src/main/java/org/myrobotlab/service/RasPi.java @@ -49,7 +49,7 @@ * More Info : http://pi4j.com/ * */ -public class RasPi extends AbstractMicrocontroller implements I2CController, GpioPinListenerDigital { +public class RasPi extends AbstractMicrocontroller implements I2CController, GpioPinListenerDigital { public static class I2CDeviceMap { transient public I2CBus bus; diff --git a/src/main/java/org/myrobotlab/service/Rekognition.java b/src/main/java/org/myrobotlab/service/Rekognition.java index dd7b2bfed5..70f9f550ba 100644 --- a/src/main/java/org/myrobotlab/service/Rekognition.java +++ b/src/main/java/org/myrobotlab/service/Rekognition.java @@ -12,6 +12,7 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import com.amazonaws.auth.AWSCredentials; @@ -33,7 +34,7 @@ import com.amazonaws.services.rekognition.model.Label; import com.amazonaws.util.IOUtils; -public class Rekognition extends Service { +public class Rekognition extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Relay.java b/src/main/java/org/myrobotlab/service/Relay.java index bd7c40edb5..c56999f2d0 100644 --- a/src/main/java/org/myrobotlab/service/Relay.java +++ b/src/main/java/org/myrobotlab/service/Relay.java @@ -6,9 +6,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class Relay extends Service { +public class Relay extends Service { public Arduino arduino; public Integer pin; diff --git a/src/main/java/org/myrobotlab/service/RoboClaw.java b/src/main/java/org/myrobotlab/service/RoboClaw.java index a26d3dfbe8..83203b99f8 100644 --- a/src/main/java/org/myrobotlab/service/RoboClaw.java +++ b/src/main/java/org/myrobotlab/service/RoboClaw.java @@ -16,6 +16,7 @@ import org.myrobotlab.serial.CRC; import org.myrobotlab.service.Pid.PidData; import org.myrobotlab.service.abstracts.AbstractMotorController; +import org.myrobotlab.service.config.MotorConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.MotorController; import org.myrobotlab.service.interfaces.PortConnector; @@ -54,7 +55,7 @@ * this value IS correct * */ -public class RoboClaw extends AbstractMotorController implements EncoderPublisher, PortConnector, MotorController, SerialDataListener { +public class RoboClaw extends AbstractMotorController implements EncoderPublisher, PortConnector, MotorController, SerialDataListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Roomba.java b/src/main/java/org/myrobotlab/service/Roomba.java index 504b9184f8..66b8e9bffb 100644 --- a/src/main/java/org/myrobotlab/service/Roomba.java +++ b/src/main/java/org/myrobotlab/service/Roomba.java @@ -32,6 +32,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.roomba.RoombaComm; import org.myrobotlab.roomba.RoombaCommPort; +import org.myrobotlab.service.config.RoombaConfig; import org.slf4j.Logger; /** @@ -41,7 +42,7 @@ * * More Info: RoombaComm */ -public class Roomba extends Service { +public class Roomba extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Ros.java b/src/main/java/org/myrobotlab/service/Ros.java index 0c16a500aa..0d6b9a2a1c 100644 --- a/src/main/java/org/myrobotlab/service/Ros.java +++ b/src/main/java/org/myrobotlab/service/Ros.java @@ -36,7 +36,7 @@ * @author GroG * */ -public class Ros extends Service implements RemoteMessageHandler, ConnectionEventListener { +public class Ros extends Service implements RemoteMessageHandler, ConnectionEventListener { /** * @see https://github.com/biobotus/rosbridge_suite/blob/master/ROSBRIDGE_PROTOCOL.md @@ -148,8 +148,8 @@ public Ros(String n, String id) { } @Override - public ServiceConfig apply(ServiceConfig c) { - RosConfig config = (RosConfig) super.apply(c); + public RosConfig apply(RosConfig c) { + super.apply(c); if (config.connect) { connect(config.bridgeUrl); if (config.subscriptions != null) { @@ -185,8 +185,8 @@ public void disconnect() { } @Override - public ServiceConfig getConfig() { - RosConfig config = (RosConfig) super.getConfig(); + public RosConfig getConfig() { + super.getConfig(); config.connect = connected; return config; } diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index bd8782961e..5842a6b3cd 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -61,6 +61,7 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceReservation; import org.myrobotlab.framework.Status; +import org.myrobotlab.framework.interfaces.ConfigurableService; import org.myrobotlab.framework.interfaces.MessageListener; import org.myrobotlab.framework.interfaces.NameProvider; import org.myrobotlab.framework.interfaces.ServiceInterface; @@ -84,7 +85,6 @@ import org.myrobotlab.process.Launcher; import org.myrobotlab.service.config.RuntimeConfig; import org.myrobotlab.service.config.ServiceConfig; -import org.myrobotlab.service.config.ServiceConfig.Listener; import org.myrobotlab.service.data.Locale; import org.myrobotlab.service.data.ServiceTypeNameResults; import org.myrobotlab.service.interfaces.ConnectionManager; @@ -122,7 +122,7 @@ * VAR OF RUNTIME ! * */ -public class Runtime extends Service implements MessageListener, ServiceLifeCyclePublisher, RemoteMessageHandler, ConnectionManager, Gateway, LocaleProvider { +public class Runtime extends Service implements MessageListener, ServiceLifeCyclePublisher, RemoteMessageHandler, ConnectionManager, Gateway, LocaleProvider { final static private long serialVersionUID = 1L; // FIXME - AVOID STATIC FIELDS !!! use .getInstance() to get the singleton @@ -138,7 +138,8 @@ public class Runtime extends Service implements MessageListener, ServiceLifeCycl * to start and configure new services. The master plan is an accumulation of * all these requests. */ - final Plan masterPlan = new Plan("runtime"); + @Deprecated /* use the filesystem only no memory plan */ + transient final Plan masterPlan = new Plan("runtime"); /** * thread for non-blocking install of services @@ -174,7 +175,7 @@ public class Runtime extends Service implements MessageListener, ServiceLifeCycl "org.myrobotlab.service.interfaces.ServiceLifeCycleListener", "org.myrobotlab.framework.interfaces.StatePublisher")); protected final Set serviceTypes = new HashSet<>(); - + /** * The directory name currently being used for config. This is NOT full path * name. It cannot be null, it cannot have "/" or "\" in the name - it has to @@ -420,7 +421,7 @@ synchronized private static Map createServicesFromPlan // Plan's config RuntimeConfig plansRtConfig = (RuntimeConfig) plan.get("runtime"); // current Runtime config - RuntimeConfig currentConfig = (RuntimeConfig) Runtime.getInstance().config; + RuntimeConfig currentConfig = Runtime.getInstance().config; for (String service : plansRtConfig.getRegistry()) { // FIXME - determine if you want to return a complete merge of activated @@ -436,10 +437,16 @@ synchronized private static Map createServicesFromPlan sc.state = "CREATING"; ServiceInterface si = createService(service, sc.type, null); sc.state = "CREATED"; - si.setConfig(sc); - si.apply(sc); + // process the base listeners/subscription of ServiceConfig + si.addConfigListeners(sc); + if (si.getName().equals("mouth")) + { + log.info("here"); + } + if (si instanceof ConfigurableService) { + ((ConfigurableService)si).apply(sc); + } createdServices.put(service, si); - // si.startService(); bad idea currentConfig.add(service); } @@ -2647,9 +2654,7 @@ public String publishFinishedConfig(String configName) { */ synchronized static public ServiceInterface start(String name, String type) { try { - if (name.equals("proxy")) { - log.info("herex"); - } + ServiceInterface requestedService = Runtime.getService(name); if (requestedService != null) { @@ -2686,7 +2691,8 @@ synchronized static public ServiceInterface start(String name, String type) { } if (requestedService == null) { - log.error("here"); + Runtime.getInstance().error("could not start %s of type %s", name, type); + return null; } // getConfig() was problematic here for JMonkeyEngine @@ -3977,21 +3983,21 @@ public void addConnection(String uuid, String id, Connection connection) { addRoute(id, uuid, 10); } - @Override - public ServiceConfig getFilteredConfig() { - RuntimeConfig sc = (RuntimeConfig) super.getFilteredConfig(); - Set removeList = new HashSet<>(); - for (Listener listener : sc.listeners) { - if (listener.callback.equals("onReleased") || listener.callback.equals("onStarted") || listener.callback.equals("onRegistered") || listener.callback.equals("onStopped") - || listener.callback.equals("onCreated")) { - removeList.add(listener); - } - } - for (Listener remove : removeList) { - sc.listeners.remove(remove); - } - return sc; - } +// @Override +// public ServiceConfig getFilteredConfig() { +// RuntimeConfig sc = (RuntimeConfig) super.getFilteredConfig(); +// Set removeList = new HashSet<>(); +// for (Listener listener : sc.listeners) { +// if (listener.callback.equals("onReleased") || listener.callback.equals("onStarted") || listener.callback.equals("onRegistered") || listener.callback.equals("onStopped") +// || listener.callback.equals("onCreated")) { +// removeList.add(listener); +// } +// } +// for (Listener remove : removeList) { +// sc.listeners.remove(remove); +// } +// return sc; +// } /** * Unregister all connections that a specified client has made. @@ -4879,9 +4885,12 @@ public String setAllIds(String id) { return id; } + @Override - public ServiceConfig apply(ServiceConfig c) { - RuntimeConfig config = (RuntimeConfig) super.apply(c); + public RuntimeConfig apply(RuntimeConfig c) { + super.apply(c); + config = c; + setLocale(config.locale); if (config.id != null) { @@ -5027,9 +5036,6 @@ public boolean saveService(String configName, String serviceName, String filenam } for (String s : servicesToSave) { - if (CodecUtils.getShortName(s).equals("i01")) { - log.info("here"); - } ServiceInterface si = getService(s); // TODO - switch to save "NON FILTERED" config !!!! // get filtered clone of config for saving @@ -5342,4 +5348,10 @@ public String getConfigPath() { return CONFIG_ROOT + fs + configName; } + @Override + public RuntimeConfig getConfig() { + config = super.getConfig(); + return config; + } + } diff --git a/src/main/java/org/myrobotlab/service/Sabertooth.java b/src/main/java/org/myrobotlab/service/Sabertooth.java index 3173c767af..652e05649d 100644 --- a/src/main/java/org/myrobotlab/service/Sabertooth.java +++ b/src/main/java/org/myrobotlab/service/Sabertooth.java @@ -13,6 +13,7 @@ import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.PortConnector; import org.myrobotlab.service.interfaces.SerialDevice; + import org.slf4j.Logger; /** @@ -30,7 +31,7 @@ * @author GroG * */ -public class Sabertooth extends AbstractMotorController implements PortConnector { +public class Sabertooth extends AbstractMotorController implements PortConnector { private static final long serialVersionUID = 1L; @@ -300,8 +301,8 @@ public void detach() { } @Override - public ServiceConfig getConfig() { - SabertoothConfig config = (SabertoothConfig)super.getConfig(); + public SabertoothConfig getConfig() { + super.getConfig(); // FIXME - remove fields and use config only config.port = getSerialPort(); config.connect = isConnected; @@ -309,8 +310,8 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { - SabertoothConfig config = (SabertoothConfig) super.apply(c); + public SabertoothConfig apply(SabertoothConfig c) { + super.apply(c); if (config.connect) { try { connect(config.port); diff --git a/src/main/java/org/myrobotlab/service/Security.java b/src/main/java/org/myrobotlab/service/Security.java index 811ee0d51a..0219c7ca53 100644 --- a/src/main/java/org/myrobotlab/service/Security.java +++ b/src/main/java/org/myrobotlab/service/Security.java @@ -47,13 +47,14 @@ import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.AuthorizationProvider; import org.myrobotlab.service.interfaces.KeyConsumer; import org.slf4j.Logger; // controlling export is "nice" but its control messages are the most important to mediate -public class Security extends Service implements AuthorizationProvider { +public class Security extends Service implements AuthorizationProvider { protected Set serviceKeyNames = new HashSet<>(); @@ -772,4 +773,16 @@ public void addServiceKeyNames(String[] keyNamesIn) { } } + @Override + public ServiceConfig apply(ServiceConfig c) { + super.apply(c); + config = c; + return config; + } + + @Override + public ServiceConfig getConfig() { + return config; + } + } diff --git a/src/main/java/org/myrobotlab/service/SegmentDisplay.java b/src/main/java/org/myrobotlab/service/SegmentDisplay.java index 2934846d96..9bad17635e 100644 --- a/src/main/java/org/myrobotlab/service/SegmentDisplay.java +++ b/src/main/java/org/myrobotlab/service/SegmentDisplay.java @@ -5,9 +5,10 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class SegmentDisplay extends Service { +public class SegmentDisplay extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/SensorMonitor.java b/src/main/java/org/myrobotlab/service/SensorMonitor.java index f2dfd3b02c..ed8e79b25b 100644 --- a/src/main/java/org/myrobotlab/service/SensorMonitor.java +++ b/src/main/java/org/myrobotlab/service/SensorMonitor.java @@ -36,6 +36,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.Pin; import org.myrobotlab.service.data.Trigger; import org.slf4j.Logger; @@ -47,7 +48,7 @@ * would be triggered if a sensor goes above or below some threshold. * */ -public class SensorMonitor extends Service { +public class SensorMonitor extends Service { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Serial.java b/src/main/java/org/myrobotlab/service/Serial.java index bf93bea2d9..5df86bede3 100644 --- a/src/main/java/org/myrobotlab/service/Serial.java +++ b/src/main/java/org/myrobotlab/service/Serial.java @@ -47,7 +47,7 @@ * Serial - a service that allows reading and writing to a serial port device. * */ -public class Serial extends Service implements SerialControl, QueueSource, SerialDataListener, RecordControl, SerialDevice, PortPublisher, PortConnector { +public class Serial extends Service implements SerialControl, QueueSource, SerialDataListener, RecordControl, SerialDevice, PortPublisher, PortConnector { /** * general read timeout - 0 is infinite > 0 is number of milliseconds to @@ -1294,16 +1294,16 @@ public void stopTcpServer() throws IOException { } @Override - public ServiceConfig getConfig() { - SerialConfig config = (SerialConfig) super.getConfig(); + public SerialConfig getConfig() { + super.getConfig(); // FIXME remove fields and use config only config.port = lastPortName; return config; } @Override - public ServiceConfig apply(ServiceConfig c) { - SerialConfig config = (SerialConfig) super.apply(c); + public SerialConfig apply(SerialConfig c) { + super.apply(c); if (config.port != null) { try { diff --git a/src/main/java/org/myrobotlab/service/SerialRelay.java b/src/main/java/org/myrobotlab/service/SerialRelay.java index 7f0f371e44..6c2d97a7fd 100644 --- a/src/main/java/org/myrobotlab/service/SerialRelay.java +++ b/src/main/java/org/myrobotlab/service/SerialRelay.java @@ -11,13 +11,14 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.SerialRelayData; import org.myrobotlab.service.interfaces.SerialDataListener; import org.myrobotlab.service.interfaces.SerialDevice; import org.myrobotlab.service.interfaces.SerialRelayListener; import org.slf4j.Logger; -public class SerialRelay extends Service implements SerialDevice, Attachable { +public class SerialRelay extends Service implements SerialDevice, Attachable { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Servo.java b/src/main/java/org/myrobotlab/service/Servo.java index 42c4c3677e..3e0b46ce95 100644 --- a/src/main/java/org/myrobotlab/service/Servo.java +++ b/src/main/java/org/myrobotlab/service/Servo.java @@ -61,7 +61,7 @@ * */ -public class Servo extends AbstractServo implements ServiceLifeCycleListener { +public class Servo extends AbstractServo implements ServiceLifeCycleListener { private static final long serialVersionUID = 1L; @@ -83,7 +83,6 @@ public Servo(String n, String id) { */ @Override protected boolean processMove(Double newPos, boolean blocking, Long timeoutMs) { - ServoConfig c = (ServoConfig) super.getFilteredConfig(); if (newPos == null) { log.info("servo processMove(null) not valid position"); return false; @@ -104,7 +103,7 @@ protected boolean processMove(Double newPos, boolean blocking, Long timeoutMs) { firstMove = false; } - if (c.autoDisable && !enabled) { + if (config.autoDisable && !enabled) { // if the servo was disable with a timer - re-enable it enable(); } @@ -197,7 +196,7 @@ protected boolean processMove(Double newPos, boolean blocking, Long timeoutMs) { sleep(blockingTimeMs); isBlocking = false; isMoving = false; - if (c.autoDisable) { + if (config.autoDisable) { // and start our countdown addTaskOneShot(idleTimeout, "disable"); } diff --git a/src/main/java/org/myrobotlab/service/ServoMixer.java b/src/main/java/org/myrobotlab/service/ServoMixer.java index 9f39bbe703..dc6c3f7127 100755 --- a/src/main/java/org/myrobotlab/service/ServoMixer.java +++ b/src/main/java/org/myrobotlab/service/ServoMixer.java @@ -36,7 +36,7 @@ * @author GroG, kwatters, others... * */ -public class ServoMixer extends Service implements ServiceLifeCycleListener, SelectListener { +public class ServoMixer extends Service implements ServiceLifeCycleListener, SelectListener { /** * The Player plays a requested gesture, which is a sequence of Poses. diff --git a/src/main/java/org/myrobotlab/service/Shoutbox.java b/src/main/java/org/myrobotlab/service/Shoutbox.java index 0376e96665..a567ae073e 100644 --- a/src/main/java/org/myrobotlab/service/Shoutbox.java +++ b/src/main/java/org/myrobotlab/service/Shoutbox.java @@ -28,10 +28,11 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.programab.Response; import org.myrobotlab.service.Xmpp.XmppMsg; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; // FIXME - use Peers ! -public class Shoutbox extends Service { +public class Shoutbox extends Service { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(Shoutbox.class); diff --git a/src/main/java/org/myrobotlab/service/SlackBot.java b/src/main/java/org/myrobotlab/service/SlackBot.java index 7d61214f9d..990fc21855 100755 --- a/src/main/java/org/myrobotlab/service/SlackBot.java +++ b/src/main/java/org/myrobotlab/service/SlackBot.java @@ -26,7 +26,7 @@ * A slack bot gateway for utterance publishers and listeners. * */ -public class SlackBot extends Service implements UtteranceListener, UtterancePublisher { +public class SlackBot extends Service implements UtteranceListener, UtterancePublisher { public final static Logger log = LoggerFactory.getLogger(SlackBot.class); @@ -43,8 +43,8 @@ public SlackBot(String reservedKey, String inId) { } @Override - public ServiceConfig getConfig() { - SlackBotConfig config = (SlackBotConfig)super.getConfig(); + public SlackBotConfig getConfig() { + super.getConfig(); // FIXME remove members and use config only config.appToken = appToken; config.botToken = botToken; @@ -52,7 +52,7 @@ public ServiceConfig getConfig() { } @Override - public ServiceConfig apply(ServiceConfig c) { + public SlackBotConfig apply(SlackBotConfig c) { SlackBotConfig config = (SlackBotConfig) super.apply(c); appToken = config.appToken; botToken = config.botToken; diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index cd455375b5..e912365efb 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -26,7 +26,6 @@ import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.core.CoreContainer; - import org.bytedeco.opencv.opencv_core.IplImage; import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.document.Document; @@ -67,7 +66,7 @@ * @author kwatters * */ -public class Solr extends Service implements DocumentListener, TextListener, MessageListener { +public class Solr extends Service implements DocumentListener, TextListener, MessageListener { private static final String CORE_NAME = "core1"; @@ -1164,9 +1163,9 @@ public void releaseService() { } @Override - public ServiceConfig apply(ServiceConfig inConfig) { + public SolrConfig apply(SolrConfig inConfig) { // - SolrConfig config = (SolrConfig)super.apply(inConfig); + super.apply(inConfig); if (config.embedded) { // try { @@ -1181,11 +1180,9 @@ public ServiceConfig apply(ServiceConfig inConfig) { } @Override - public ServiceConfig getConfig() { - // return our config - // we need to create - SolrConfig config = (SolrConfig)super.getConfig(); - // config.embedded = this.embedded + public SolrConfig getConfig() { + super.getConfig(); + if (this.embeddedSolrServer != null) { config.embedded = true; } @@ -1196,8 +1193,5 @@ public ServiceConfig getConfig() { return config; } - - - // Config support } diff --git a/src/main/java/org/myrobotlab/service/Sphinx.java b/src/main/java/org/myrobotlab/service/Sphinx.java index 192f5206a3..af7a082a68 100644 --- a/src/main/java/org/myrobotlab/service/Sphinx.java +++ b/src/main/java/org/myrobotlab/service/Sphinx.java @@ -68,7 +68,7 @@ * */ @Deprecated /* we need another offline solution - one that doesn't suck */ -public class Sphinx extends AbstractSpeechRecognizer { +public class Sphinx extends AbstractSpeechRecognizer { class SpeechProcessor extends Thread { Sphinx myService = null; diff --git a/src/main/java/org/myrobotlab/service/SpotMicro.java b/src/main/java/org/myrobotlab/service/SpotMicro.java index 7a61a7065a..5c3f84f515 100644 --- a/src/main/java/org/myrobotlab/service/SpotMicro.java +++ b/src/main/java/org/myrobotlab/service/SpotMicro.java @@ -5,10 +5,9 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.ServiceConfig; -import org.myrobotlab.service.config.ServoConfig; import org.slf4j.Logger; -public class SpotMicro extends Service { +public class SpotMicro extends Service { private static final long serialVersionUID = 1L; @@ -18,16 +17,6 @@ public SpotMicro(String n, String id) { super(n, id); } - @Override - public ServiceConfig apply(ServiceConfig c) { - return c; - } - - @Override - public ServiceConfig getConfig() { - return config; - } - public static void main(String[] args) { try { diff --git a/src/main/java/org/myrobotlab/service/Test.java b/src/main/java/org/myrobotlab/service/Test.java index d3bf958054..50460a63eb 100644 --- a/src/main/java/org/myrobotlab/service/Test.java +++ b/src/main/java/org/myrobotlab/service/Test.java @@ -33,6 +33,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.process.GitHub; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.StatusListener; import org.myrobotlab.service.meta.abstracts.MetaData; import org.slf4j.Logger; @@ -55,7 +56,7 @@ * method appended is a callback * */ -public class Test extends Service implements StatusListener { +public class Test extends Service implements StatusListener { /** * filter services by availabilities diff --git a/src/main/java/org/myrobotlab/service/UltrasonicSensor.java b/src/main/java/org/myrobotlab/service/UltrasonicSensor.java index eaa57a4576..8a3c1025a0 100644 --- a/src/main/java/org/myrobotlab/service/UltrasonicSensor.java +++ b/src/main/java/org/myrobotlab/service/UltrasonicSensor.java @@ -30,7 +30,7 @@ * UltrasonicSensor implements RangeListener just for testing purposes * */ -public class UltrasonicSensor extends Service implements RangeListener, RangePublisher, UltrasonicSensorControl { +public class UltrasonicSensor extends Service implements RangeListener, RangePublisher, UltrasonicSensorControl { private final static Logger log = LoggerFactory.getLogger(UltrasonicSensor.class); @@ -162,7 +162,7 @@ public Set getAttached() { @Override public UltrasonicSensorConfig getConfig() { - UltrasonicSensorConfig config = (UltrasonicSensorConfig)super.getConfig(); + super.getConfig(); // FIXME - remove member variables use config directly config.controller = controllerName; config.triggerPin = trigPin; @@ -234,8 +234,8 @@ protected boolean isAttached(UltrasonicSensorController controller) { } @Override - public ServiceConfig apply(ServiceConfig c) { - UltrasonicSensorConfig config = (UltrasonicSensorConfig) super.apply(c); + public UltrasonicSensorConfig apply(UltrasonicSensorConfig c) { + super.apply(c); if (config.triggerPin != null) setTriggerPin(config.triggerPin); diff --git a/src/main/java/org/myrobotlab/service/VideoStreamer.java b/src/main/java/org/myrobotlab/service/VideoStreamer.java index f21acbebb6..b47256e5ca 100644 --- a/src/main/java/org/myrobotlab/service/VideoStreamer.java +++ b/src/main/java/org/myrobotlab/service/VideoStreamer.java @@ -11,6 +11,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.MjpegServer; import org.myrobotlab.service.abstracts.AbstractVideoSink; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.VideoSource; import org.slf4j.Logger; @@ -28,7 +29,7 @@ */ public class VideoStreamer - extends AbstractVideoSink /* extends Service implements VideoSink */ { + extends AbstractVideoSink /* extends Service implements VideoSink */ { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/VirtualArduino.java b/src/main/java/org/myrobotlab/service/VirtualArduino.java index 4fb23d38ec..8d53922761 100644 --- a/src/main/java/org/myrobotlab/service/VirtualArduino.java +++ b/src/main/java/org/myrobotlab/service/VirtualArduino.java @@ -13,6 +13,7 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.PinDefinition; import org.myrobotlab.service.interfaces.PortConnector; import org.myrobotlab.service.interfaces.PortListener; @@ -28,7 +29,7 @@ * @author GroG * */ -public class VirtualArduino extends Service implements PortPublisher, PortListener, PortConnector, SerialDataListener { +public class VirtualArduino extends Service implements PortPublisher, PortListener, PortConnector, SerialDataListener { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(VirtualArduino.class); diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 904a71796f..e3756fa9e2 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -34,7 +34,6 @@ import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.MRLListener; import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.MethodCache; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; @@ -45,7 +44,6 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.BareBonesBrowserLaunch; import org.myrobotlab.net.Connection; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.config.WebGuiConfig; import org.myrobotlab.service.interfaces.AuthorizationProvider; import org.myrobotlab.service.interfaces.Gateway; @@ -64,7 +62,7 @@ * services are already APIs - perhaps a data API - same as service without the * message wrapper */ -public class WebGui extends Service implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { +public class WebGui extends Service implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { public static class LiveVideoStreamHandler implements Handler { @@ -91,7 +89,7 @@ public void handle(AtmosphereResource r) { } } } - + private final transient IncomingMsgQueue inMsgQueue = new IncomingMsgQueue(); public static class Panel { @@ -174,6 +172,7 @@ static public String getApiKey(String uri) { // just marking as transient to remove some of the data load 10240 max frame transient Map panels = new HashMap(); + // FIXME - add as a config member public Integer port; public String root = "root"; @@ -326,10 +325,8 @@ public Config.Builder getNettosphereConfig() { configBuilder.resource("/stream", stream); - WebGuiConfig c = (WebGuiConfig) config; - // add all webgui resource directories - for (String resource : c.resources) { + for (String resource : config.resources) { configBuilder.resource(resource); } @@ -1154,18 +1151,17 @@ public void stopMdns() { } @Override - public ServiceConfig getConfig() { - WebGuiConfig config = (WebGuiConfig) super.getConfig(); - // FIXME - remove member variables use config only + // FIXME port and autoStartBrowser should just be part of config + // then this override can be removed + public WebGuiConfig getConfig() { config.port = port; config.autoStartBrowser = autoStartBrowser; return config; } - @Override - public ServiceConfig apply(ServiceConfig c) { - WebGuiConfig config = (WebGuiConfig) super.apply(c); - + public WebGuiConfig apply(WebGuiConfig c) { + super.apply(c); + if (config.port != null && (port != null && config.port.intValue() != port.intValue())) { setPort(config.port); } @@ -1302,4 +1298,6 @@ public void onStopped(String name) { @Override public void onReleased(String name) { } + + } \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/service/WebSocketConnector.java b/src/main/java/org/myrobotlab/service/WebSocketConnector.java index 995ebc7cf1..dacf200686 100644 --- a/src/main/java/org/myrobotlab/service/WebSocketConnector.java +++ b/src/main/java/org/myrobotlab/service/WebSocketConnector.java @@ -9,6 +9,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.Connection; import org.myrobotlab.net.WsClient; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.ConnectionManager; import org.myrobotlab.service.interfaces.RemoteMessageHandler; import org.myrobotlab.service.interfaces.TextPublisher; @@ -18,7 +19,7 @@ * @author MaVo (MyRobotLab) / LunDev (GitHub) */ -public class WebSocketConnector extends Service implements RemoteMessageHandler, ConnectionManager, TextPublisher { +public class WebSocketConnector extends Service implements RemoteMessageHandler, ConnectionManager, TextPublisher { static final long serialVersionUID = 1L; static final Logger log = LoggerFactory.getLogger(WebSocketConnector.class); diff --git a/src/main/java/org/myrobotlab/service/Webcam.java b/src/main/java/org/myrobotlab/service/Webcam.java index f56c1bf8bb..d20faf8804 100644 --- a/src/main/java/org/myrobotlab/service/Webcam.java +++ b/src/main/java/org/myrobotlab/service/Webcam.java @@ -12,6 +12,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.geometry.Point; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.string.StringUtil; import org.slf4j.Logger; @@ -20,7 +21,7 @@ import com.github.sarxos.webcam.WebcamStreamer; import com.github.sarxos.webcam.ds.v4l4j.V4l4jDriver; -public class Webcam extends Service implements WebcamListener { +public class Webcam extends Service implements WebcamListener { protected class VideoProcessor implements Runnable { diff --git a/src/main/java/org/myrobotlab/service/WebkitSpeechSynthesis.java b/src/main/java/org/myrobotlab/service/WebkitSpeechSynthesis.java index b5306a69d7..d13a23468e 100644 --- a/src/main/java/org/myrobotlab/service/WebkitSpeechSynthesis.java +++ b/src/main/java/org/myrobotlab/service/WebkitSpeechSynthesis.java @@ -9,6 +9,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; +import org.myrobotlab.service.config.SpeechSynthesisConfig; import org.myrobotlab.service.data.AudioData; import org.slf4j.Logger; @@ -20,7 +21,7 @@ * @author GroG * */ -public class WebkitSpeechSynthesis extends AbstractSpeechSynthesis { +public class WebkitSpeechSynthesis extends AbstractSpeechSynthesis { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Wii.java b/src/main/java/org/myrobotlab/service/Wii.java index ebba625bcf..7447b38d06 100644 --- a/src/main/java/org/myrobotlab/service/Wii.java +++ b/src/main/java/org/myrobotlab/service/Wii.java @@ -34,6 +34,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; import jssc.SerialPortEvent; @@ -64,7 +65,7 @@ * http://www.bot-thoughts.com/2010/12/connecting-mbed-to-wiimote-ir-camera.html * */ -public class Wii extends Service implements WiimoteListener, SerialPortEventListener { +public class Wii extends Service implements WiimoteListener, SerialPortEventListener { public static class IRData implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Wikipedia.java b/src/main/java/org/myrobotlab/service/Wikipedia.java index 6686d35ed6..414fbef7ca 100644 --- a/src/main/java/org/myrobotlab/service/Wikipedia.java +++ b/src/main/java/org/myrobotlab/service/Wikipedia.java @@ -5,7 +5,6 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Map; import java.util.List; import java.util.Map; @@ -59,7 +58,7 @@ * @author GroG * */ -public class Wikipedia extends Service implements SearchPublisher, ImagePublisher, TextPublisher { +public class Wikipedia extends Service implements SearchPublisher, ImagePublisher, TextPublisher { public final static Logger log = LoggerFactory.getLogger(Wikipedia.class); diff --git a/src/main/java/org/myrobotlab/service/WorkE.java b/src/main/java/org/myrobotlab/service/WorkE.java index 489ddf6eab..9bede4d64b 100644 --- a/src/main/java/org/myrobotlab/service/WorkE.java +++ b/src/main/java/org/myrobotlab/service/WorkE.java @@ -13,6 +13,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.opencv.OpenCVData; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis.WordFilter; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.interfaces.JoystickListener; import org.myrobotlab.service.interfaces.MotorControl; @@ -25,7 +26,7 @@ import org.myrobotlab.service.interfaces.TextPublisher; import org.slf4j.Logger; -public class WorkE extends Service implements StatusListener, TextPublisher, SpeechSynthesisControl, SpeechSynthesisControlPublisher, JoystickListener { +public class WorkE extends Service implements StatusListener, TextPublisher, SpeechSynthesisControl, SpeechSynthesisControlPublisher, JoystickListener { public final static Logger log = LoggerFactory.getLogger(WorkE.class); diff --git a/src/main/java/org/myrobotlab/service/Xmpp.java b/src/main/java/org/myrobotlab/service/Xmpp.java index 82d0ceb964..46f6b6b029 100644 --- a/src/main/java/org/myrobotlab/service/Xmpp.java +++ b/src/main/java/org/myrobotlab/service/Xmpp.java @@ -42,6 +42,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.Connection; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.Gateway; import org.slf4j.Logger; @@ -54,7 +55,7 @@ * @author GROG * */ -public class Xmpp extends Service implements Gateway, ChatManagerListener, ChatMessageListener, MessageListener, RosterListener, ConnectionListener {// , +public class Xmpp extends Service implements Gateway, ChatManagerListener, ChatMessageListener, MessageListener, RosterListener, ConnectionListener {// , public static class Contact { public String user; @@ -99,9 +100,11 @@ public XmppMsg(Chat chat, Message msg) { String serviceName = "myrobotlab.org"; // xmpp.myrobotlab.org int port = 5222; - transient XMPPTCPConnectionConfiguration config; + transient XMPPTCPConnectionConfiguration configx; transient XMPPTCPConnection connection; transient ChatManager chatManager; + + protected ServiceConfig config; transient Roster roster = null; diff --git a/src/main/java/org/myrobotlab/service/_TemplateService.java b/src/main/java/org/myrobotlab/service/_TemplateService.java index 0c90dd27a6..61798b307e 100644 --- a/src/main/java/org/myrobotlab/service/_TemplateService.java +++ b/src/main/java/org/myrobotlab/service/_TemplateService.java @@ -7,7 +7,7 @@ import org.myrobotlab.service.config.ServiceConfig; import org.slf4j.Logger; -public class _TemplateService extends Service { +public class _TemplateService extends Service { private static final long serialVersionUID = 1L; @@ -23,14 +23,13 @@ public _TemplateService(String n, String id) { *
       @Override
       public ServiceConfig apply(ServiceConfig c) {
    -    // _TemplateServiceConfig config = (_TemplateService)super.apply(c);
    -    // if more complex config handling is needed
    +    super.apply(c)
         return c;
       }
     
       @Override
       public ServiceConfig getConfig() {
    -    // _TemplateServiceConfig config = (_TemplateService)super.getConfig();
    +    super.getConfig()
         return config;
       }
       
    diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java index d410874c8b..0329c6c707 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java @@ -1,8 +1,9 @@ package org.myrobotlab.service.abstracts; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.ComputerVision; -public abstract class AbstractComputerVision extends AbstractVideoSource implements ComputerVision { +public abstract class AbstractComputerVision extends AbstractVideoSource implements ComputerVision { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractMicrocontroller.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractMicrocontroller.java index f564ffb25d..2beafb2958 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractMicrocontroller.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractMicrocontroller.java @@ -9,13 +9,14 @@ import org.myrobotlab.arduino.BoardInfo; import org.myrobotlab.arduino.BoardType; import org.myrobotlab.framework.Service; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.PinData; import org.myrobotlab.service.interfaces.Microcontroller; import org.myrobotlab.service.interfaces.PinArrayListener; import org.myrobotlab.service.interfaces.PinDefinition; import org.myrobotlab.service.interfaces.PinListener; -public abstract class AbstractMicrocontroller extends Service implements Microcontroller { +public abstract class AbstractMicrocontroller extends Service implements Microcontroller { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java index c4fd8ea595..2b9e794a54 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java @@ -59,7 +59,7 @@ * */ -abstract public class AbstractMotor extends Service implements MotorControl, EncoderListener { +abstract public class AbstractMotor extends Service implements MotorControl, EncoderListener { private static final long serialVersionUID = 1L; @@ -135,7 +135,7 @@ public Set refreshControllers() { } public MotorController getController() { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; return (MotorController) Runtime.getService(c.controller); } @@ -147,13 +147,13 @@ public double getPowerLevel() { @Override public boolean isAttached(MotorController controller) { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; return controller.getName().equals(c.controller); } @Override public boolean isInverted() { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; return c.mapper.maxIn < c.mapper.minOut; } @@ -169,10 +169,10 @@ public void move(double powerInput) { info("%s is locked - will not move"); return; } - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; // FIXME make mapper.isInInputRange(x) - double min = (c.mapper.minIn < c.mapper.maxIn) ? c.mapper.minIn : c.mapper.maxIn; - double max = (c.mapper.minIn < c.mapper.maxIn) ? c.mapper.maxIn : c.mapper.minIn; + double min = Math.min(c.mapper.minIn, c.mapper.maxIn); + double max = Math.max(c.mapper.minIn, c.mapper.maxIn); if (powerInput < min) { warn("requested power %.2f is under minimum %.2f", powerInput, c.mapper.minIn); @@ -211,7 +211,7 @@ public double publishPowerOutputChange(double output) { @Override public void setInverted(boolean invert) { log.warn("setting {} inverted = {}", getName(), invert); - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; double temp = c.mapper.minIn; c.mapper.minIn = c.mapper.maxIn; c.mapper.maxIn = temp; @@ -220,7 +220,7 @@ public void setInverted(boolean invert) { @Override public void setMinMax(double min, double max) { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; c.mapper.minIn = min; c.mapper.maxIn = max; info("updated min %.2f max %.2f", min, max); @@ -228,7 +228,7 @@ public void setMinMax(double min, double max) { } public void map(double minX, double maxX, double minY, double maxY) { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; c.mapper.map(minX, maxX, minY, maxY); broadcastState(); } @@ -304,7 +304,7 @@ public void setEncoder(EncoderPublisher encoder) { @Override public void detachMotorController(MotorController controller) { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; controller.detach(this); controller = null; c.controller = null; @@ -350,7 +350,7 @@ public void attachMotorController(MotorController controller) throws Exception { } log.info("attachMotorController {}", controller.getName()); - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; c.controller = controller.getName(); motorPorts = controller.getPorts(); // TODO: KW: set a reasonable mapper. for pwm motor it's probable -1 to 1 to @@ -385,7 +385,7 @@ public boolean isAttached() { @Override public void detach() { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; c.controller = null; // MAKE NOTE!: don't want to do this anymore for fear of infinit detach loop // just detach this service @@ -398,7 +398,7 @@ public void detach() { // TODO - this could be Java 8 default interface implementation @Override public void detach(String name) { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; MotorController controller = getController(); if (controller == null || !name.equals(controller.getName())) { @@ -422,7 +422,7 @@ public boolean isAttached(String name) { @Override public Set getAttached() { - HashSet ret = new HashSet(); + HashSet ret = new HashSet<>(); MotorController controller = getController(); if (controller != null) { ret.add(controller.getName()); @@ -432,33 +432,33 @@ public Set getAttached() { // FIXME promote to interface public Mapper getMapper() { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; return c.mapper; } // FIXME promote to interface public void setMapper(MapperSimple mapper) { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; c.mapper = mapper; } // FIXME promote to interface @Override public double calcControllerOutput() { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; return c.mapper.calcOutput(getPowerLevel()); } @Override public void setAxis(String name) { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; c.axis = name; broadcastState(); } @Override public String getAxis() { - GeneralMotorConfig c = (GeneralMotorConfig) config; + GeneralMotorConfig c = config; return c.axis; } @@ -468,8 +468,8 @@ public void onAnalog(AnalogData data) { } @Override - public ServiceConfig apply(ServiceConfig c) { - GeneralMotorConfig config = (GeneralMotorConfig) super.apply(c); + public C apply(C c) { + GeneralMotorConfig config = super.apply(c); // config.mapper = new MapperLinear(config.minIn, config.maxIn, // config.minOut, config.maxOut); diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java index 62b4ea5aca..5f01ebc74f 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java @@ -8,10 +8,11 @@ import org.myrobotlab.math.MapperLinear; import org.myrobotlab.math.interfaces.Mapper; import org.myrobotlab.service.Runtime; +import org.myrobotlab.service.config.MotorConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.MotorController; -public abstract class AbstractMotorController extends Service implements MotorController { +public abstract class AbstractMotorController extends Service implements MotorController { /** * currently attached motors to this controller diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractPinEncoder.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractPinEncoder.java index b04a551f16..7ebc91a687 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractPinEncoder.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractPinEncoder.java @@ -2,10 +2,11 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.sensor.EncoderData; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.EncoderControl; import org.myrobotlab.service.interfaces.EncoderController; -public abstract class AbstractPinEncoder extends Service implements EncoderControl { +public abstract class AbstractPinEncoder extends Service implements EncoderControl { private static final long serialVersionUID = 1L; public String pin; diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java index b98891a08c..aeb9d4aec5 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java @@ -1,5 +1,6 @@ package org.myrobotlab.service.abstracts; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -16,7 +17,6 @@ import org.myrobotlab.sensor.EncoderPublisher; import org.myrobotlab.sensor.TimeEncoder; import org.myrobotlab.service.Runtime; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.config.ServoConfig; import org.myrobotlab.service.data.AngleData; import org.myrobotlab.service.data.ServoMove; @@ -53,7 +53,7 @@ * outputs. * */ -public abstract class AbstractServo extends Service implements ServoControl, ServoControlPublisher, ServoStatusPublisher, EncoderPublisher { +public abstract class AbstractServo extends Service implements ServoControl, ServoControlPublisher, ServoStatusPublisher, EncoderPublisher { public final static Logger log = LoggerFactory.getLogger(AbstractServo.class); @@ -294,8 +294,7 @@ public void attach(EncoderControl enc) throws Exception { } public void setController(String name) { - ServoConfig c = (ServoConfig) config; - c.controller = name; + config.controller = name; broadcastState(); } @@ -323,8 +322,6 @@ public AngleData publishJointAngle(AngleData angle) { */ @Override public void attachServoController(String service) { - ServoConfig c = (ServoConfig) config; - if (service == null) { error("attachServoController null"); return; @@ -335,7 +332,7 @@ public void attachServoController(String service) { return; } - if (isAttached && !CodecUtils.getFullName(service).equals(c.controller)) { + if (isAttached && !CodecUtils.getFullName(service).equals(CodecUtils.getFullName(config.controller))) { warn("%s already attached to %s detach first", getName(), service); return; } else if (isAttached) { @@ -350,8 +347,10 @@ public void attachServoController(String service) { addListener("publishServoSetSpeed", service); addListener("publishServoEnable", service); addListener("publishServoDisable", service); - - c.controller = service; + if (CodecUtils.isLocal(service)) { + service = CodecUtils.getShortName(service); + } + config.controller = service; // "guessing" its ok if it exists ... if (Runtime.getService(service) != null) { @@ -366,7 +365,7 @@ public void attachServoController(String service) { send(service, "attach", getName()); log.info("{} attached to {} on pin {}", getName(), service, pin); - if (c.autoDisable) { + if (config.autoDisable) { disable(); addTaskOneShot(idleTimeout, "disable"); } @@ -379,8 +378,7 @@ public void attachServoController(String service) { */ @Override public void detach() { - ServoConfig c = (ServoConfig) config; - detach(c.controller); + detach(config.controller); } @Override @@ -398,14 +396,12 @@ public void detach(ServoController sc) { */ @Override public void detach(String controllerName) { - ServoConfig c = (ServoConfig) config; - if (!isAttached) { log.info("already detached"); return; } - if (c.controller != null && !c.controller.equals(controllerName)) { + if (config.controller != null && !config.controller.equals(controllerName)) { log.warn("{} not attached to {}", getName(), controllerName); return; } @@ -444,8 +440,7 @@ public void disable() { @Override public void enable() { - ServoConfig c = (ServoConfig) config; - if (c.autoDisable) { + if (config.autoDisable) { if (!isMoving) { // not moving - safe & expected to put in a disable purgeTask("disable"); @@ -465,14 +460,12 @@ public void fullSpeed() { @Override public boolean isAutoDisable() { - ServoConfig c = (ServoConfig) config; - return c.autoDisable; + return config.autoDisable; } @Override public String getController() { - ServoConfig c = (ServoConfig) config; - return c.controller; + return config.controller; } @Override @@ -559,14 +552,12 @@ public boolean isAttached() { @Override public boolean isAttached(Attachable attachable) { - ServoConfig c = (ServoConfig) config; - return isAttached && c.controller.equals(attachable.getName()); + return isAttached && config.controller.equals(attachable.getName()); } @Override public boolean isAttached(String name) { - ServoConfig c = (ServoConfig) config; - return isAttached && CodecUtils.getFullName(c.controller).equals(CodecUtils.getFullName(name)); + return isAttached && CodecUtils.getFullName(config.controller).equals(CodecUtils.getFullName(name)); } @Override @@ -816,7 +807,6 @@ public void rest() { */ @Override public void setAutoDisable(boolean autoDisable) { - ServoConfig c = (ServoConfig) config; if (autoDisable) { if (!isMoving) { // not moving - safe & expected to put in a disable @@ -825,8 +815,8 @@ public void setAutoDisable(boolean autoDisable) { } else { purgeTask("disable"); } - boolean valueChanged = c.autoDisable != autoDisable; - c.autoDisable = autoDisable; + boolean valueChanged = config.autoDisable != autoDisable; + config.autoDisable = autoDisable; if (valueChanged) { broadcastState(); } @@ -1065,11 +1055,10 @@ public ServoEvent publishServoStarted(String name, Double position) { @Override public ServoEvent publishServoStopped(String name, Double position) { log.debug("{} publishServoStopped({}, {})", System.currentTimeMillis(), name, position); - ServoConfig c = (ServoConfig) config; // log.info("TIME-ENCODER SERVO_STOPPED - {}", name); // if currently configured to autoDisable - the timer starts now // if we are "stopping" going from moving to not moving - if (c.autoDisable && isMoving) { + if (config.autoDisable && isMoving) { // we cancel any pre-existing timer if it exists purgeTask("disable"); // and start our countdown @@ -1144,8 +1133,8 @@ public void attachServoControlListener(String name) { } @Override - public ServiceConfig apply(ServiceConfig c) { - ServoConfig config = (ServoConfig) super.apply(c); + public C apply(C c) { + super.apply(c); // important - if starting up // and autoDisable - then the assumption at this point @@ -1171,9 +1160,7 @@ public ServiceConfig apply(ServiceConfig c) { if (config.synced != null) { syncedServos.clear(); - for (String s : config.synced) { - syncedServos.add(s); - } + Collections.addAll(syncedServos, config.synced); } // rest = config.rest; @@ -1204,9 +1191,9 @@ public ServiceConfig apply(ServiceConfig c) { } @Override - public ServiceConfig getConfig() { + public C getConfig() { - ServoConfig config = (ServoConfig) super.getConfig(); + super.getConfig(); config.enabled = enabled; @@ -1227,7 +1214,7 @@ public ServiceConfig getConfig() { config.sweepMax = sweepMax; config.sweepMin = sweepMin; - if (syncedServos.size() > 0) { + if (!syncedServos.isEmpty()) { config.synced = new String[syncedServos.size()]; int i = 0; for (String s : syncedServos) { diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java index b87bd7daae..c6003bc01c 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java @@ -16,7 +16,7 @@ import org.myrobotlab.service.interfaces.SpeechSynthesis; import org.myrobotlab.service.interfaces.TextListener; -public abstract class AbstractSpeechRecognizer extends Service implements SpeechRecognizer { +public abstract class AbstractSpeechRecognizer extends Service implements SpeechRecognizer { /** * text and confidence (and any additional meta data) to be published @@ -575,18 +575,18 @@ public void unsetWakeWord() { } @Override - public ServiceConfig getConfig() { - SpeechRecognizerConfig c = (SpeechRecognizerConfig) super.getConfig(); + public C getConfig() { + C c = super.getConfig(); c.listening = isListening(); c.wakeWord = getWakeWord(); Set listeners = getAttached("publishText"); - c.textListeners = listeners.toArray(new String[listeners.size()]); + c.textListeners = listeners.toArray(new String[0]); return c; } @Override - public ServiceConfig apply(ServiceConfig c) { - SpeechRecognizerConfig config = (SpeechRecognizerConfig)super.apply(c);; + public C apply(C c) { + C config = super.apply(c); setWakeWord(config.wakeWord); if (config.listening) { startListening(); diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java index d70210df21..741c94aca9 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java @@ -33,7 +33,7 @@ import org.myrobotlab.service.interfaces.TextPublisher; import org.slf4j.Logger; -public abstract class AbstractSpeechSynthesis extends Service implements SpeechSynthesis, TextListener, KeyConsumer, AudioListener { +public abstract class AbstractSpeechSynthesis extends Service implements SpeechSynthesis, TextListener, KeyConsumer, AudioListener { private static final long serialVersionUID = 1L; @@ -1112,8 +1112,8 @@ public boolean isMute() { } @Override - public ServiceConfig apply(ServiceConfig c) { - SpeechSynthesisConfig config = (SpeechSynthesisConfig) super.apply(c); + public C apply(C c) { + super.apply(c); setMute(config.mute); @@ -1154,11 +1154,11 @@ public void attachSpeechControl(SpeechSynthesisControl control) { } @Override - public ServiceConfig getConfig() { - SpeechSynthesisConfig c = (SpeechSynthesisConfig) super.getConfig(); + public C getConfig() { + C c = super.getConfig(); c.mute = mute; c.blocking = blocking; - if (substitutions != null && substitutions.size() > 0) { + if (substitutions != null && !substitutions.isEmpty()) { c.substitutions = new HashMap<>(); c.substitutions.putAll(substitutions); } @@ -1166,7 +1166,7 @@ public ServiceConfig getConfig() { c.voice = voice.name; } Set listeners = getAttached("publishStartSpeaking"); - c.speechRecognizers = listeners.toArray(new String[listeners.size()]); + c.speechRecognizers = listeners.toArray(new String[0]); return c; } diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSink.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSink.java index 47246ae947..9850d58630 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSink.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSink.java @@ -3,10 +3,11 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.image.SerializableImage; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.VideoSink; import org.myrobotlab.service.interfaces.VideoSource; -public abstract class AbstractVideoSink extends Service implements VideoSink { +public abstract class AbstractVideoSink extends Service implements VideoSink { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSource.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSource.java index a1a236bc0d..18e359d1b6 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSource.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractVideoSource.java @@ -1,10 +1,11 @@ package org.myrobotlab.service.abstracts; import org.myrobotlab.framework.Service; +import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.VideoSink; import org.myrobotlab.service.interfaces.VideoSource; -public abstract class AbstractVideoSource extends Service implements VideoSource { +public abstract class AbstractVideoSource extends Service implements VideoSource { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/config/EmojiConfig.java b/src/main/java/org/myrobotlab/service/config/EmojiConfig.java index ff8dd2d3f2..e2d6cbbdcc 100644 --- a/src/main/java/org/myrobotlab/service/config/EmojiConfig.java +++ b/src/main/java/org/myrobotlab/service/config/EmojiConfig.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; -import org.myrobotlab.framework.Peer; import org.myrobotlab.framework.Plan; public class EmojiConfig extends ServiceConfig { diff --git a/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java b/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java index 9bd54ce192..42db50689b 100644 --- a/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java +++ b/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java @@ -2,7 +2,7 @@ import org.myrobotlab.framework.Plan; -public class SabertoothConfig extends ServiceConfig { +public class SabertoothConfig extends MotorConfig { public String port; public boolean connect = false; diff --git a/src/main/java/org/myrobotlab/string/StringUtil.java b/src/main/java/org/myrobotlab/string/StringUtil.java index 6ea3e26531..06c152b8db 100644 --- a/src/main/java/org/myrobotlab/string/StringUtil.java +++ b/src/main/java/org/myrobotlab/string/StringUtil.java @@ -251,4 +251,14 @@ public static String intArrayToString(int[] ints) { return builder.toString(); } + public static String removeEnd(final String str, final String remove) { + if (str == null || str.length() == 0 || remove == null || remove.length() == 0) { + return str; + } + if (str.endsWith(remove)) { + return str.substring(0, str.length() - remove.length()); + } + return str; + } + } diff --git a/src/main/resources/resource/WebGui/app/service/js/ServoGui.js b/src/main/resources/resource/WebGui/app/service/js/ServoGui.js index aad2fc6554..2175f81481 100644 --- a/src/main/resources/resource/WebGui/app/service/js/ServoGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/ServoGui.js @@ -124,6 +124,10 @@ angular.module('mrlapp.service.ServoGui', []).controller('ServoGuiCtrl', ['$scop // max range of slider bar } + if (service?.config?.controller){ + $scope.servoOptions.attachName = service?.config?.controller + } + $scope.idleSeconds = service.idleTimeout / 1000 // $scope.pos.options.minLimit = service.mapper.minX // $scope.pos.options.maxLimit = service.mapper.maxX diff --git a/src/test/java/org/myrobotlab/framework/ServiceTest.java b/src/test/java/org/myrobotlab/framework/ServiceTest.java index 6e0af04b91..a9f180362c 100644 --- a/src/test/java/org/myrobotlab/framework/ServiceTest.java +++ b/src/test/java/org/myrobotlab/framework/ServiceTest.java @@ -1,18 +1,19 @@ package org.myrobotlab.framework; -import org.junit.Test; -import org.myrobotlab.service.config.ServiceConfig; -import org.myrobotlab.test.AbstractTest; +import static org.junit.Assert.assertEquals; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import static org.junit.Assert.assertEquals; +import org.junit.Test; +import org.myrobotlab.service.config.ServiceConfig; +import org.myrobotlab.test.AbstractTest; public class ServiceTest extends AbstractTest { - public static class TestService extends Service { + public static class TestService extends Service { + + private static final long serialVersionUID = 1L; /** * Constructor of service, reservedkey typically is a services name and inId @@ -35,10 +36,11 @@ public void testConfigListenerFiltering() { new MRLListener("meth", "random@test-id", "onMeth"), new MRLListener("meth", "random2@test-2-id", "onMeth") ); - t.setConfig(new ServiceConfig()); + t.apply(new ServiceConfig()); t.outbox.notifyList = Map.of("meth", listeners); - List filtered = t.getConfig().listeners; + List filtered = t.getFilteredConfig().listeners; assertEquals("random", filtered.get(0).listener); assertEquals("random2@test-2-id", filtered.get(1).listener); + t.getFilteredConfig(); } } diff --git a/src/test/java/org/myrobotlab/service/ServoTest.java b/src/test/java/org/myrobotlab/service/ServoTest.java index b13b560892..74bb958eda 100644 --- a/src/test/java/org/myrobotlab/service/ServoTest.java +++ b/src/test/java/org/myrobotlab/service/ServoTest.java @@ -344,6 +344,7 @@ public void testHelperMethods() throws Exception { servo01.setPin(7); servo01.setPin(8); servo01.setSpeed(1.0); + servo01.detach(); servo01.setController("blah"); assertEquals("blah", servo01.getController()); servo01.attach("arduino01"); From 1d01e49398c98636192329665ef04cecc2132b3a Mon Sep 17 00:00:00 2001 From: grog Date: Sat, 12 Aug 2023 22:14:31 -0700 Subject: [PATCH 38/54] fixed a couple wrong config clean up others --- .../org/myrobotlab/framework/Service.java | 2 +- .../service/Adafruit16CServoDriver.java | 1 - .../java/org/myrobotlab/service/Clock.java | 7 ++- .../java/org/myrobotlab/service/Cron.java | 5 +- .../org/myrobotlab/service/DiscordBot.java | 17 ++----- .../myrobotlab/service/DocumentPipeline.java | 19 ++++---- .../org/myrobotlab/service/FileConnector.java | 7 ++- .../service/FiniteStateMachine.java | 8 ++-- .../myrobotlab/service/GoogleTranslate.java | 19 -------- .../java/org/myrobotlab/service/Hd44780.java | 13 +++-- .../java/org/myrobotlab/service/I2cMux.java | 14 +++--- .../org/myrobotlab/service/ImageDisplay.java | 6 +-- .../java/org/myrobotlab/service/InMoov2.java | 16 ++----- .../java/org/myrobotlab/service/Joystick.java | 9 ++-- .../org/myrobotlab/service/MotorDualPwm.java | 14 +++--- .../org/myrobotlab/service/MotorHat4Pi.java | 2 +- .../org/myrobotlab/service/MouthControl.java | 20 ++++---- .../java/org/myrobotlab/service/Mpr121.java | 12 ++--- .../java/org/myrobotlab/service/OpenCV.java | 18 +++---- .../myrobotlab/service/OpenWeatherMap.java | 8 ++-- .../java/org/myrobotlab/service/Pcf8574.java | 13 +++-- .../org/myrobotlab/service/ProgramAB.java | 22 ++++----- .../java/org/myrobotlab/service/Python.java | 9 ++-- .../java/org/myrobotlab/service/Random.java | 8 ++-- src/main/java/org/myrobotlab/service/Ros.java | 8 ++-- .../org/myrobotlab/service/Sabertooth.java | 6 +-- .../java/org/myrobotlab/service/Serial.java | 5 +- .../java/org/myrobotlab/service/Solr.java | 10 ++-- .../java/org/myrobotlab/service/Tracking.java | 16 +++---- .../myrobotlab/service/UltrasonicSensor.java | 17 ++++--- .../java/org/myrobotlab/service/WebGui.java | 10 ++-- .../service/abstracts/AbstractMotor.java | 4 +- .../service/abstracts/AbstractServo.java | 48 +++++++++---------- .../abstracts/AbstractSpeechRecognizer.java | 13 +++-- .../abstracts/AbstractSpeechSynthesis.java | 19 ++++---- .../myrobotlab/service/meta/JoystickMeta.java | 4 +- 36 files changed, 193 insertions(+), 236 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index fe7dfd9450..b6080d2c32 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -1427,7 +1427,7 @@ public T apply(T c) { * processes those listeners and adds them to the outbox notifyList. */ public ServiceConfig addConfigListeners(ServiceConfig config) { - if (config.listeners != null) { + if (config != null && config.listeners != null) { for (Listener listener : config.listeners) { addListener(listener.method, listener.listener, listener.callback); } diff --git a/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java b/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java index 78da519eab..290e3fd8b7 100644 --- a/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java +++ b/src/main/java/org/myrobotlab/service/Adafruit16CServoDriver.java @@ -24,7 +24,6 @@ import org.myrobotlab.math.MapperLinear; import org.myrobotlab.math.interfaces.Mapper; import org.myrobotlab.service.config.Adafruit16CServoDriverConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.ServoMove; import org.myrobotlab.service.data.ServoSpeed; import org.myrobotlab.service.interfaces.I2CControl; diff --git a/src/main/java/org/myrobotlab/service/Clock.java b/src/main/java/org/myrobotlab/service/Clock.java index df211bb4e3..14fd02b2f3 100644 --- a/src/main/java/org/myrobotlab/service/Clock.java +++ b/src/main/java/org/myrobotlab/service/Clock.java @@ -203,15 +203,14 @@ public Integer getInterval() { public ClockConfig apply(ClockConfig c) { super.apply(c); - config = (ClockConfig)c; - if (config.running != null) { - if (config.running) { + if (c.running != null) { + if (c.running) { startClock(); } else { stopClock(); } } - return config; + return c; } public void restartClock() { diff --git a/src/main/java/org/myrobotlab/service/Cron.java b/src/main/java/org/myrobotlab/service/Cron.java index 7e8e0ba00b..9faaabe0d0 100644 --- a/src/main/java/org/myrobotlab/service/Cron.java +++ b/src/main/java/org/myrobotlab/service/Cron.java @@ -188,14 +188,13 @@ public String addTask(Task task) { @Override public CronConfig apply(CronConfig c) { - super.apply(config); + super.apply(c); // deschedule current tasks removeAllTasks(); // add new tasks - CronConfig config = (CronConfig)c; - for (Task task : config.tasks) { + for (Task task : c.tasks) { addTask(task); } return c; diff --git a/src/main/java/org/myrobotlab/service/DiscordBot.java b/src/main/java/org/myrobotlab/service/DiscordBot.java index 3770877e84..f3aa3560cf 100644 --- a/src/main/java/org/myrobotlab/service/DiscordBot.java +++ b/src/main/java/org/myrobotlab/service/DiscordBot.java @@ -57,24 +57,17 @@ public DiscordBot(String reservedKey, String inId) { public DiscordBotConfig apply(DiscordBotConfig c) { super.apply(c); - if (config.token != null) { - setToken(config.token); + if (c.token != null) { + setToken(c.token); } - // REMOVED - OVERLAP WITH SUBSCRIPTIONS -// if (config.utteranceListeners != null) { -// for (String name : config.utteranceListeners) { -// attachUtteranceListener(name); -// } -// } - - if (config.connect && config.token != null && !config.token.isEmpty()) { + if (c.connect && c.token != null && !c.token.isEmpty()) { connect(); - } else if (config.token == null || config.token.isEmpty()) { + } else if (c.token == null || c.token.isEmpty()) { error("requires valid token to connect"); } - return config; + return c; } public String getBotName() { diff --git a/src/main/java/org/myrobotlab/service/DocumentPipeline.java b/src/main/java/org/myrobotlab/service/DocumentPipeline.java index 86dc854cf5..0d511776c4 100644 --- a/src/main/java/org/myrobotlab/service/DocumentPipeline.java +++ b/src/main/java/org/myrobotlab/service/DocumentPipeline.java @@ -18,7 +18,7 @@ import org.myrobotlab.service.interfaces.DocumentListener; import org.myrobotlab.service.interfaces.DocumentPublisher; -public class DocumentPipeline extends Service implements DocumentListener, DocumentPublisher { +public class DocumentPipeline extends Service implements DocumentListener, DocumentPublisher { private static final long serialVersionUID = 1L; @@ -135,7 +135,7 @@ public static void main(String[] args) throws Exception { fieldNameMap.put("dc_title", "title"); fieldNameMap.put("xmpdm_album", "album"); stage3Config.setMapProperty("fieldNameMap", fieldNameMap); - workflowConfig.addStage(stage3Config);; + workflowConfig.addStage(stage3Config);; // TODO: rename more fields.. // TODO: delete unnecessary fields. pipeline.setConfig(workflowConfig); @@ -211,23 +211,22 @@ public void publishFlush() { } @Override - public ServiceConfig apply(ServiceConfig inConfig) { - DocumentPipelineConfig config = (DocumentPipelineConfig)super.apply(inConfig); - // - this.workFlowConfig = config.workFlowConfig; + public DocumentPipelineConfig apply(DocumentPipelineConfig c) { + super.apply(c); + + this.workFlowConfig = c.workFlowConfig; try { initalize(); } catch (ClassNotFoundException e) { log.error("Error initializing the document pipeline.", e); // TODO: shoiuld we throw some runtime here? } - return config; + return c; } @Override - public ServiceConfig getConfig() { - // return the config - DocumentPipelineConfig config = (DocumentPipelineConfig)super.getConfig(); + public DocumentPipelineConfig getConfig() { + super.getConfig(); config.workFlowConfig = this.workFlowConfig; return config; } diff --git a/src/main/java/org/myrobotlab/service/FileConnector.java b/src/main/java/org/myrobotlab/service/FileConnector.java index a83f9e1209..3b9d9c95d6 100644 --- a/src/main/java/org/myrobotlab/service/FileConnector.java +++ b/src/main/java/org/myrobotlab/service/FileConnector.java @@ -125,11 +125,10 @@ public void setDirectory(String directory) { } @Override - public ServiceConfig apply(ServiceConfig inConfig) { - // - FileConnectorConfig config = (FileConnectorConfig)super.apply(inConfig); + public ServiceConfig apply(ServiceConfig c) { + super.apply(c); // anything else? - return config; + return c; } @Override diff --git a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java index 08d1ed45a4..a093f61565 100644 --- a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java +++ b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java @@ -245,20 +245,20 @@ public FiniteStateMachineConfig apply(FiniteStateMachineConfig c) { // when config is "applied" we need to copy out and // re-apply the config using addTransition List newTransistions = new ArrayList<>(); - newTransistions.addAll(config.transitions); + newTransistions.addAll(c.transitions); clear(); for (Transition t : newTransistions) { addTransition(t.from, t.event, t.to); } messageListeners = new HashSet<>(); - messageListeners.addAll(config.messageListeners); + messageListeners.addAll(c.messageListeners); broadcastState(); } // setCurrent - if (config.current != null) { - setCurrent(config.current); + if (c.current != null) { + setCurrent(c.current); } return c; diff --git a/src/main/java/org/myrobotlab/service/GoogleTranslate.java b/src/main/java/org/myrobotlab/service/GoogleTranslate.java index 2d322eabcf..ce892a709e 100644 --- a/src/main/java/org/myrobotlab/service/GoogleTranslate.java +++ b/src/main/java/org/myrobotlab/service/GoogleTranslate.java @@ -58,25 +58,6 @@ public String translate(String text) throws FileNotFoundException, IOException { } - /** - * The methods apply and getConfig can be used, if more complex configuration handling is needed. - * By default, the framework takes care of most of it, including subscription handling. - *
    -  @Override
    -  public ServiceConfig apply(ServiceConfig c) {
    -    // _TemplateServiceConfig config = (_TemplateService)super.apply(c);
    -    // if more complex config handling is needed
    -    return c;
    -  }
    -
    -  @Override
    -  public ServiceConfig getConfig() {
    -    // _TemplateServiceConfig config = (_TemplateService)super.getConfig();
    -    return config;
    -  }
    -  
    - **/ - public static void main(String[] args) { try { diff --git a/src/main/java/org/myrobotlab/service/Hd44780.java b/src/main/java/org/myrobotlab/service/Hd44780.java index 43dfde676f..9294fee992 100644 --- a/src/main/java/org/myrobotlab/service/Hd44780.java +++ b/src/main/java/org/myrobotlab/service/Hd44780.java @@ -11,7 +11,6 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.Hd44780Config; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.TextListener; import org.slf4j.Logger; @@ -123,6 +122,10 @@ public Hd44780(String n, String id) { @Override public void attach(String name) { ServiceInterface si = Runtime.getService(name); + if (si == null) { + error("could not attach to %s not found", name); + return; + } if (si instanceof Pcf8574) { attachPcf8574((Pcf8574) si); return; @@ -736,16 +739,16 @@ public Hd44780Config getConfig() { public Hd44780Config apply(Hd44780Config c) { super.apply(c); - if (config.controller != null) { + if (c.controller != null) { try { - attach(config.controller); + attach(c.controller); } catch (Exception e) { error(e); } } - if (pcf != null && config.backlight != null && config.backlight) { - setBackLight(config.backlight); + if (pcf != null && c.backlight != null && c.backlight) { + setBackLight(c.backlight); } return c; } diff --git a/src/main/java/org/myrobotlab/service/I2cMux.java b/src/main/java/org/myrobotlab/service/I2cMux.java index 4f6c24b43e..c3403a1eb4 100644 --- a/src/main/java/org/myrobotlab/service/I2cMux.java +++ b/src/main/java/org/myrobotlab/service/I2cMux.java @@ -363,7 +363,7 @@ public String getAddress() { @Override public I2cMuxConfig getConfig() { - I2cMuxConfig config = (I2cMuxConfig)super.getConfig(); + super.getConfig(); // FIXME this should only be in config, no need for local fields config.bus = deviceBus; config.address = deviceAddress; @@ -374,13 +374,13 @@ public I2cMuxConfig getConfig() { @Override public I2cMuxConfig apply(I2cMuxConfig c) { - I2cMuxConfig config = (I2cMuxConfig) super.apply(c); + super.apply(c); // FIXME - remove all this, it should "only" be in config - deviceBus = config.bus; - deviceAddress = config.address; - i2cDevices = config.i2cDevices; - if (config.controller != null) { - controllerName = config.controller; + deviceBus = c.bus; + deviceAddress = c.address; + i2cDevices = c.i2cDevices; + if (c.controller != null) { + controllerName = c.controller; } return c; } diff --git a/src/main/java/org/myrobotlab/service/ImageDisplay.java b/src/main/java/org/myrobotlab/service/ImageDisplay.java index ded65ab7b7..d115227158 100644 --- a/src/main/java/org/myrobotlab/service/ImageDisplay.java +++ b/src/main/java/org/myrobotlab/service/ImageDisplay.java @@ -81,13 +81,13 @@ public void actionPerformed(ActionEvent arg0) { @Override public ImageDisplayConfig apply(ImageDisplayConfig c) { super.apply(c); - if (config.displays != null) { - for (String displayName : config.displays.keySet()) { + if (c.displays != null) { + for (String displayName : c.displays.keySet()) { close(displayName); displayInternal(displayName); } } - return config; + return c; } @Override diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 520077321f..51dac5ae44 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -157,19 +157,19 @@ public InMoov2Config apply(InMoov2Config c) { locales = Locale.getLocaleMap("en-US", "fr-FR", "es-ES", "de-DE", "nl-NL", "ru-RU", "hi-IN", "it-IT", "fi-FI", "pt-PT", "tr-TR"); - if (config.locale != null) { - setLocale(config.locale); + if (c.locale != null) { + setLocale(c.locale); } else { setLocale(getSupportedLocale(Runtime.getInstance().getLocale().toString())); } loadInitScripts(); - if (config.loadGestures) { + if (c.loadGestures) { loadGestures(); } - if (config.heartbeat) { + if (c.heartbeat) { startHeartbeat(); } else { stopHeartbeat(); @@ -181,13 +181,7 @@ public InMoov2Config apply(InMoov2Config c) { return c; } - // TODO- Hook to get config event published - // public void applyConfig() { - // super.apply(); - // log.error("applyConfig()"); - // // always getResponse ! - // speak("InMoov apply config"); - // } + @Override public void attachTextListener(String name) { diff --git a/src/main/java/org/myrobotlab/service/Joystick.java b/src/main/java/org/myrobotlab/service/Joystick.java index 9b7a1f8064..0e8bee108a 100644 --- a/src/main/java/org/myrobotlab/service/Joystick.java +++ b/src/main/java/org/myrobotlab/service/Joystick.java @@ -728,16 +728,15 @@ public JoystickConfig apply(JoystickConfig c) { getControllers(); // get controller request from config - JoystickConfig config = (JoystickConfig)super.apply(c); - if (config.controller != null) { + if (c.controller != null) { setController(config.controller); } // stupid transform from array to set - yaml wants array, set prevents // duplicates :( - if (config.analogListeners != null) { - for (String id : config.analogListeners.keySet()) { - ArrayList list = config.analogListeners.get(id); + if (c.analogListeners != null) { + for (String id : c.analogListeners.keySet()) { + ArrayList list = c.analogListeners.get(id); Set s = analogListeners.get(id); if (s == null) { s = new HashSet<>(); diff --git a/src/main/java/org/myrobotlab/service/MotorDualPwm.java b/src/main/java/org/myrobotlab/service/MotorDualPwm.java index 10687ba938..fe4764ae6d 100644 --- a/src/main/java/org/myrobotlab/service/MotorDualPwm.java +++ b/src/main/java/org/myrobotlab/service/MotorDualPwm.java @@ -80,16 +80,16 @@ public MotorDualPwmConfig getConfig() { public MotorDualPwmConfig apply(MotorDualPwmConfig c) { super.apply(c); - if (config.leftPwmPin != null) { - setLeftPwmPin(config.leftPwmPin); + if (c.leftPwmPin != null) { + setLeftPwmPin(c.leftPwmPin); } - if (config.rightPwmPin != null) { - setRightPwmPin(config.rightPwmPin); + if (c.rightPwmPin != null) { + setRightPwmPin(c.rightPwmPin); } - if (config.pwmFreq != null) { - setPwmFreq(config.pwmFreq); + if (c.pwmFreq != null) { + setPwmFreq(c.pwmFreq); } - return config; + return c; } public static void main(String[] args) throws InterruptedException { diff --git a/src/main/java/org/myrobotlab/service/MotorHat4Pi.java b/src/main/java/org/myrobotlab/service/MotorHat4Pi.java index 6862aaf807..1bc2bb6c1d 100644 --- a/src/main/java/org/myrobotlab/service/MotorHat4Pi.java +++ b/src/main/java/org/myrobotlab/service/MotorHat4Pi.java @@ -99,7 +99,7 @@ public MotorHat4PiConfig getConfig() { public MotorHat4PiConfig apply(MotorHat4PiConfig c) { super.apply(c); - setMotor(config.motorId); + setMotor(c.motorId); return c; } diff --git a/src/main/java/org/myrobotlab/service/MouthControl.java b/src/main/java/org/myrobotlab/service/MouthControl.java index f89ac8ce3e..9d07a7c55e 100644 --- a/src/main/java/org/myrobotlab/service/MouthControl.java +++ b/src/main/java/org/myrobotlab/service/MouthControl.java @@ -249,20 +249,20 @@ public MouthControlConfig getConfig() { public MouthControlConfig apply(MouthControlConfig c) { super.apply(c); // FIXME - remove local fields, use config only - mouthClosedPos = config.mouthClosedPos; - mouthOpenedPos = config.mouthOpenedPos; - delaytime = config.delaytime; - delaytimestop = config.delaytimestop; - delaytimeletter = config.delaytimeletter; - jaw = config.jaw; - neoPixel = config.neoPixel; + mouthClosedPos = c.mouthClosedPos; + mouthOpenedPos = c.mouthOpenedPos; + delaytime = c.delaytime; + delaytimestop = c.delaytimestop; + delaytimeletter = c.delaytimeletter; + jaw = c.jaw; + neoPixel = c.neoPixel; // mouth needs to attach to us // it needs to create notify entries // so we fire a message to attach to us - mouth = config.mouth; - if (config.mouth != null) { - mouth = config.mouth; + mouth = c.mouth; + if (c.mouth != null) { + mouth = c.mouth; send(mouth, "attach", getName()); } diff --git a/src/main/java/org/myrobotlab/service/Mpr121.java b/src/main/java/org/myrobotlab/service/Mpr121.java index b3a11ec802..d71ca4da60 100644 --- a/src/main/java/org/myrobotlab/service/Mpr121.java +++ b/src/main/java/org/myrobotlab/service/Mpr121.java @@ -929,16 +929,16 @@ public void setDeviceAddress(String deviceAddress) { public Mpr121Config apply(Mpr121Config c) { super.apply(c); // FIXME remove local fields in favor of config only - if (config.address != null) { - setAddress(config.address); + if (c.address != null) { + setAddress(c.address); } - if (config.bus != null) { - setBus(config.bus); + if (c.bus != null) { + setBus(c.bus); } - if (config.controller != null) { + if (c.controller != null) { try { - attach(config.controller); + attach(c.controller); } catch (Exception e) { error(e); } diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index c73b4413c3..7a26a17dca 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -2042,24 +2042,24 @@ public OpenCVConfig getConfig() { @Override public OpenCVConfig apply(OpenCVConfig c) { super.apply(c); - setCameraIndex(config.cameraIndex); - setGrabberType(config.grabberType); - setInputFileName(config.inputFile); - setInputSource(config.inputSource); + setCameraIndex(c.cameraIndex); + setGrabberType(c.grabberType); + setInputFileName(c.inputFile); + setInputSource(c.inputSource); - setNativeViewer(config.nativeViewer); + setNativeViewer(c.nativeViewer); - setWebViewer(config.webViewer); + setWebViewer(c.webViewer); filters.clear(); - if (config.filters != null) { - for (OpenCVFilter f : config.filters.values()) { + if (c.filters != null) { + for (OpenCVFilter f : c.filters.values()) { addFilter(f); // TODO: better configuration of the filter when it's added. } } - if (config.capturing) { + if (c.capturing) { capture(); } diff --git a/src/main/java/org/myrobotlab/service/OpenWeatherMap.java b/src/main/java/org/myrobotlab/service/OpenWeatherMap.java index 47227141f1..2a9fcb045e 100644 --- a/src/main/java/org/myrobotlab/service/OpenWeatherMap.java +++ b/src/main/java/org/myrobotlab/service/OpenWeatherMap.java @@ -263,11 +263,11 @@ public OpenWeatherMapConfig getConfig() { public OpenWeatherMapConfig apply(OpenWeatherMapConfig c) { super.apply(c); // FIXME - remove local fields in favor of only config - if (config.currentUnits != null) { - setUnits(config.currentUnits); + if (c.currentUnits != null) { + setUnits(c.currentUnits); } - if (config.currentTown != null) { - setLocation(config.currentTown); + if (c.currentTown != null) { + setLocation(c.currentTown); } return c; } diff --git a/src/main/java/org/myrobotlab/service/Pcf8574.java b/src/main/java/org/myrobotlab/service/Pcf8574.java index be2682cbba..4e7410d0d2 100644 --- a/src/main/java/org/myrobotlab/service/Pcf8574.java +++ b/src/main/java/org/myrobotlab/service/Pcf8574.java @@ -16,7 +16,6 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.Pcf8574Config; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.PinData; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; @@ -735,16 +734,16 @@ public Pcf8574Config getConfig() { public Pcf8574Config apply(Pcf8574Config c) { super.apply(c); // FIXME remove local fields in favor of config only - if (config.address != null) { - setAddress(config.address); + if (c.address != null) { + setAddress(c.address); } - if (config.bus != null) { - setBus(config.bus); + if (c.bus != null) { + setBus(c.bus); } - if (config.controller != null) { + if (c.controller != null) { try { - attach(config.controller); + attach(c.controller); } catch (Exception e) { error(e); } diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index 63aebc9030..171f67feae 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -1093,32 +1093,32 @@ public ProgramABConfig getConfig() { @Override public ProgramABConfig apply(ProgramABConfig c) { super.apply(c); - if (config.bots != null && config.bots.size() > 0) { + if (c.bots != null && c.bots.size() > 0) { // bots.clear(); - for (String botPath : config.bots) { + for (String botPath : c.bots) { addBotPath(botPath); } } - if (config.botDir == null) { - config.botDir = getResourceDir(); + if (c.botDir == null) { + c.botDir = getResourceDir(); } - List botsFromScanning = scanForBots(config.botDir); + List botsFromScanning = scanForBots(c.botDir); for (File file : botsFromScanning) { addBotPath(file.getAbsolutePath()); } - if (config.currentUserName != null) { - setCurrentUserName(config.currentUserName); + if (c.currentUserName != null) { + setCurrentUserName(c.currentUserName); } - if (config.currentBotName != null) { - setCurrentUserName(config.currentBotName); + if (c.currentBotName != null) { + setCurrentUserName(c.currentBotName); } - if (config.startTopic != null) { - setTopic(config.startTopic); + if (c.startTopic != null) { + setTopic(c.startTopic); } diff --git a/src/main/java/org/myrobotlab/service/Python.java b/src/main/java/org/myrobotlab/service/Python.java index 02ce853f15..4153d0513f 100644 --- a/src/main/java/org/myrobotlab/service/Python.java +++ b/src/main/java/org/myrobotlab/service/Python.java @@ -944,17 +944,16 @@ public void onStopped(String fullname) { public PythonConfig apply(PythonConfig c) { super.apply(c); - config = (PythonConfig)c; // apply is the first method called after construction, // since we offer the capability of executing scripts specified in config // the interpreter must be configured and created here init(); - if (config.startScripts != null && config.startScripts.size() > 0) { + if (c.startScripts != null && c.startScripts.size() > 0) { if (isRunning()) { - for (String script : config.startScripts) { + for (String script : c.startScripts) { try { execFile(script); } catch (Exception e) { @@ -966,8 +965,8 @@ public PythonConfig apply(PythonConfig c) { PySystemState sys = Py.getSystemState(); - if (config.modulePaths != null) { - for (String path : config.modulePaths) { + if (c.modulePaths != null) { + for (String path : c.modulePaths) { sys.path.append(new PyString(path)); } } diff --git a/src/main/java/org/myrobotlab/service/Random.java b/src/main/java/org/myrobotlab/service/Random.java index 4e5b368b10..93b8178fd2 100644 --- a/src/main/java/org/myrobotlab/service/Random.java +++ b/src/main/java/org/myrobotlab/service/Random.java @@ -256,11 +256,11 @@ public RandomConfig getConfig() { @Override public RandomConfig apply(RandomConfig c) { super.apply(c); - enabled = config.enabled; + enabled = c.enabled; try { - for (String key : config.randomMessages.keySet()) { - RandomMessageConfig msgc = config.randomMessages.get(key); + for (String key : c.randomMessages.keySet()) { + RandomMessageConfig msgc = c.randomMessages.get(key); addRandom(msgc.minIntervalMs, msgc.maxIntervalMs, key.substring(0, key.lastIndexOf(".")), key.substring(key.lastIndexOf(".") + 1), msgc.data); if (!msgc.enabled) { disable(key); @@ -270,7 +270,7 @@ public RandomConfig apply(RandomConfig c) { error(e); } - return config; + return c; } public RandomMessage remove(String name, String method) { diff --git a/src/main/java/org/myrobotlab/service/Ros.java b/src/main/java/org/myrobotlab/service/Ros.java index 0d6b9a2a1c..bea1ad4bc2 100644 --- a/src/main/java/org/myrobotlab/service/Ros.java +++ b/src/main/java/org/myrobotlab/service/Ros.java @@ -150,10 +150,10 @@ public Ros(String n, String id) { @Override public RosConfig apply(RosConfig c) { super.apply(c); - if (config.connect) { - connect(config.bridgeUrl); - if (config.subscriptions != null) { - for (String topic : config.subscriptions) { + if (c.connect) { + connect(c.bridgeUrl); + if (c.subscriptions != null) { + for (String topic : c.subscriptions) { rosSubscribe(topic); } } diff --git a/src/main/java/org/myrobotlab/service/Sabertooth.java b/src/main/java/org/myrobotlab/service/Sabertooth.java index 652e05649d..a022a347eb 100644 --- a/src/main/java/org/myrobotlab/service/Sabertooth.java +++ b/src/main/java/org/myrobotlab/service/Sabertooth.java @@ -9,11 +9,9 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.service.abstracts.AbstractMotorController; import org.myrobotlab.service.config.SabertoothConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.PortConnector; import org.myrobotlab.service.interfaces.SerialDevice; - import org.slf4j.Logger; /** @@ -312,9 +310,9 @@ public SabertoothConfig getConfig() { @Override public SabertoothConfig apply(SabertoothConfig c) { super.apply(c); - if (config.connect) { + if (c.connect) { try { - connect(config.port); + connect(c.port); } catch (Exception e) { error(e); } diff --git a/src/main/java/org/myrobotlab/service/Serial.java b/src/main/java/org/myrobotlab/service/Serial.java index 5df86bede3..af16f3522a 100644 --- a/src/main/java/org/myrobotlab/service/Serial.java +++ b/src/main/java/org/myrobotlab/service/Serial.java @@ -33,7 +33,6 @@ import org.myrobotlab.serial.PortStream; import org.myrobotlab.serial.SerialControl; import org.myrobotlab.service.config.SerialConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.PortConnector; import org.myrobotlab.service.interfaces.PortPublisher; import org.myrobotlab.service.interfaces.QueueSource; @@ -1305,10 +1304,10 @@ public SerialConfig getConfig() { public SerialConfig apply(SerialConfig c) { super.apply(c); - if (config.port != null) { + if (c.port != null) { try { if (isConnected()) { - connect(config.port); + connect(c.port); } } catch (Exception e) { log.error("load connecting threw", e); diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java index e912365efb..33a81a21ee 100644 --- a/src/main/java/org/myrobotlab/service/Solr.java +++ b/src/main/java/org/myrobotlab/service/Solr.java @@ -1163,10 +1163,10 @@ public void releaseService() { } @Override - public SolrConfig apply(SolrConfig inConfig) { + public SolrConfig apply(SolrConfig c) { // - super.apply(inConfig); - if (config.embedded) { + super.apply(c); + if (c.embedded) { // try { startEmbedded(); @@ -1174,9 +1174,9 @@ public SolrConfig apply(SolrConfig inConfig) { // TODO: how should we handle this? log.warn("Error starting embedded solr instance.", e); e.printStackTrace(); - }; + }; } - return config; + return c; } @Override diff --git a/src/main/java/org/myrobotlab/service/Tracking.java b/src/main/java/org/myrobotlab/service/Tracking.java index 3d05493de1..d4d2c93128 100644 --- a/src/main/java/org/myrobotlab/service/Tracking.java +++ b/src/main/java/org/myrobotlab/service/Tracking.java @@ -27,7 +27,7 @@ * @author GroG * */ -public class Tracking extends Service { +public class Tracking extends Service { private static final long serialVersionUID = 1L; @@ -274,24 +274,24 @@ public void attachCv(String cv) { } @Override - public ServiceConfig getConfig() { - TrackingConfig config = (TrackingConfig) super.getConfig(); + public TrackingConfig getConfig() { + super.getConfig(); config.enabled = (state == TrackingState.IDLE) ? false : true; return config; } @Override - public ServiceConfig apply(ServiceConfig c) { - TrackingConfig config = (TrackingConfig) super.apply(c); + public TrackingConfig apply(TrackingConfig c) { + super.apply(c); - config.lostTrackingDelayMs = lostTrackingDelayMs; + c.lostTrackingDelayMs = lostTrackingDelayMs; - if (config.enabled) { + if (c.enabled) { // enable(); } else { disable(); } - return config; + return c; } public boolean isIdle() { diff --git a/src/main/java/org/myrobotlab/service/UltrasonicSensor.java b/src/main/java/org/myrobotlab/service/UltrasonicSensor.java index 8a3c1025a0..c5df12d252 100644 --- a/src/main/java/org/myrobotlab/service/UltrasonicSensor.java +++ b/src/main/java/org/myrobotlab/service/UltrasonicSensor.java @@ -12,7 +12,6 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.config.UltrasonicSensorConfig; import org.myrobotlab.service.data.RangeData; import org.myrobotlab.service.interfaces.RangeListener; @@ -237,18 +236,18 @@ protected boolean isAttached(UltrasonicSensorController controller) { public UltrasonicSensorConfig apply(UltrasonicSensorConfig c) { super.apply(c); - if (config.triggerPin != null) - setTriggerPin(config.triggerPin); + if (c.triggerPin != null) + setTriggerPin(c.triggerPin); - if (config.echoPin != null) - setEchoPin(config.echoPin); + if (c.echoPin != null) + setEchoPin(c.echoPin); - if (config.timeout != null) - timeout = config.timeout; + if (c.timeout != null) + timeout = c.timeout; - if (config.controller != null) { + if (c.controller != null) { try { - attach(config.controller); + attach(c.controller); } catch (Exception e) { error(e); } diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index e3756fa9e2..d5e0de4efd 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -1162,14 +1162,14 @@ public WebGuiConfig getConfig() { public WebGuiConfig apply(WebGuiConfig c) { super.apply(c); - if (config.port != null && (port != null && config.port.intValue() != port.intValue())) { - setPort(config.port); + if (c.port != null && (port != null && c.port.intValue() != port.intValue())) { + setPort(c.port); } - autoStartBrowser(config.autoStartBrowser); - if (config.enableMdns) { + autoStartBrowser(c.autoStartBrowser); + if (c.enableMdns) { startMdns(); } - return config; + return c; } public static void main(String[] args) { diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java index 2b9e794a54..eb496e0d35 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotor.java @@ -477,9 +477,9 @@ public C apply(C c) { // mapper.setClip(config.clip); // FIXME ?? future use only ServiceConfig.listeners ? - if (config.controller != null) { + if (c.controller != null) { try { - attach(config.controller); + attach(c.controller); } catch (Exception e) { error(e); } diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java index aeb9d4aec5..5c57cef155 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java @@ -1140,41 +1140,41 @@ public C apply(C c) { // and autoDisable - then the assumption at this point // is it is currently disabled, otherwise it will take // a move to disable - if (config.autoDisable) { + if (c.autoDisable) { disable(); } - if (config.minIn != null && config.maxIn != null && config.minOut != null && config.maxOut != null) { - mapper = new MapperLinear(config.minIn, config.maxIn, config.minOut, config.maxOut); + if (c.minIn != null && c.maxIn != null && c.minOut != null && c.maxOut != null) { + mapper = new MapperLinear(c.minIn, c.maxIn, c.minOut, c.maxOut); } - mapper.setInverted(config.inverted); - mapper.setClip(config.clip); - enabled = config.enabled; - if (config.idleTimeout != null) { - idleTimeout = config.idleTimeout; + mapper.setInverted(c.inverted); + mapper.setClip(c.clip); + enabled = c.enabled; + if (c.idleTimeout != null) { + idleTimeout = c.idleTimeout; } - pin = config.pin; + pin = c.pin; - speed = config.speed; - sweepMax = config.sweepMax; - sweepMin = config.sweepMin; + speed = c.speed; + sweepMax = c.sweepMax; + sweepMin = c.sweepMin; - if (config.synced != null) { + if (c.synced != null) { syncedServos.clear(); - Collections.addAll(syncedServos, config.synced); + Collections.addAll(syncedServos, c.synced); } - // rest = config.rest; - if (config.rest != null) { - rest = config.rest; - targetPos = config.rest; - // currentInputP = mapper.calcOutput(config.rest); - currentInputPos = config.rest; - broadcast("publishEncoderData", new EncoderData(getName(), pin, config.rest, config.rest)); + // rest = c.rest; + if (c.rest != null) { + rest = c.rest; + targetPos = c.rest; + // currentInputP = mapper.calcOutput(c.rest); + currentInputPos = c.rest; + broadcast("publishEncoderData", new EncoderData(getName(), pin, c.rest, c.rest)); } - if (config.controller != null) { + if (c.controller != null) { try { - attach(config.controller); + attach(c.controller); } catch (Exception e) { error(e); } @@ -1182,7 +1182,7 @@ public C apply(C c) { // connect and attach on an arduino can take considerable time // so we'll add our id - if (config.autoDisable) { + if (c.autoDisable) { disable(); addTaskOneShot(idleTimeout, "disable"); } diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java index c6003bc01c..fa54857772 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechRecognizer.java @@ -8,7 +8,6 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.service.Runtime; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.config.SpeechRecognizerConfig; import org.myrobotlab.service.data.AudioData; import org.myrobotlab.service.data.Locale; @@ -586,22 +585,22 @@ public C getConfig() { @Override public C apply(C c) { - C config = super.apply(c); - setWakeWord(config.wakeWord); - if (config.listening) { + super.apply(c); + setWakeWord(c.wakeWord); + if (c.listening) { startListening(); } else { stopListening(); } - if (config.recording) { + if (c.recording) { startRecording(); } else { stopRecording(); } - if (config.textListeners != null) { - for (String listener : config.textListeners) { + if (c.textListeners != null) { + for (String listener : c.textListeners) { addListener("publishText", listener); } } diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java index 741c94aca9..50bd64ccd0 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java @@ -19,7 +19,6 @@ import org.myrobotlab.service.AudioFile; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.Security; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.config.SpeechSynthesisConfig; import org.myrobotlab.service.data.AudioData; import org.myrobotlab.service.data.Locale; @@ -1115,24 +1114,24 @@ public boolean isMute() { public C apply(C c) { super.apply(c); - setMute(config.mute); + setMute(c.mute); - setBlocking(config.blocking); + setBlocking(c.blocking); - if (config.substitutions != null) { - for (String n : config.substitutions.keySet()) { - replaceWord(n, config.substitutions.get(n)); + if (c.substitutions != null) { + for (String n : c.substitutions.keySet()) { + replaceWord(n, c.substitutions.get(n)); } } // some systems require querying set of voices getVoices(); - if (config.voice != null) { - setVoice(config.voice); + if (c.voice != null) { + setVoice(c.voice); } - if (config.speechRecognizers != null) { - for (String name : config.speechRecognizers) { + if (c.speechRecognizers != null) { + for (String name : c.speechRecognizers) { try { attachSpeechListener(name); } catch (Exception e) { diff --git a/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java b/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java index 03342dee4a..9a8d6107a8 100644 --- a/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java @@ -22,10 +22,10 @@ public JoystickMeta() { log.info("Joystick.getMetaData {} isArm() {}", platform, platform.isArm()); if (platform.isArm()) { - log.info("loading arm binaries"); + log.info("adding armv7 native dependencies"); addDependency("jinput-natives", "jinput-natives-armv7.hfp", "2.0.7", "zip"); } else { - log.info("loading non-arm binaries"); + log.info("adding jinput native dependencies"); addDependency("jinput-natives", "jinput-natives", "2.0.7", "zip"); } // addDependency("net.java.jinput", "jinput-platform", "2.0.7"); From 226dfb632d1a19ef7878d2c6c848f1f82ad21a14 Mon Sep 17 00:00:00 2001 From: GroG Date: Sun, 13 Aug 2023 07:34:55 -0700 Subject: [PATCH 39/54] Better process stream handling (#1331) * Better process stream handling * other parts * added fullname to filtering * removing status list --- .../java/org/myrobotlab/framework/Outbox.java | 51 ++++++-- .../org/myrobotlab/framework/Service.java | 13 +- .../interfaces/ServiceInterface.java | 17 ++- .../org/myrobotlab/service/LocalSpeech.java | 70 ++++++++--- .../java/org/myrobotlab/service/Runtime.java | 111 ++++++++---------- 5 files changed, 170 insertions(+), 92 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/Outbox.java b/src/main/java/org/myrobotlab/framework/Outbox.java index 775134d3b4..01921add9f 100644 --- a/src/main/java/org/myrobotlab/framework/Outbox.java +++ b/src/main/java/org/myrobotlab/framework/Outbox.java @@ -37,13 +37,13 @@ import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.interfaces.MessageListener; -import org.myrobotlab.framework.interfaces.NameProvider; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.interfaces.Gateway; import org.slf4j.Logger; + /* * Outbox is a message based thread which sends messages based on addListener lists and current * queue status. It is only aware of the Service directory, addListener lists, and operators. @@ -62,18 +62,24 @@ public class Outbox implements Runnable, Serializable { static public final String BROADCAST = "BROADCAST"; static public final String PROCESSANDBROADCAST = "PROCESSANDBROADCAST"; - NameProvider myService = null; - LinkedList msgBox = new LinkedList(); + protected String name = null; + private transient LinkedList msgBox = new LinkedList(); private boolean isRunning = false; private boolean blocking = false; int maxQueue = 1024; int initialThreadCount = 1; transient ArrayList outboxThreadPool = new ArrayList(); + protected Map filters = new HashMap<>(); + + public interface FilterInterface { + public boolean filter(Message msg); + } + /** * pub/sub listeners - HashMap < {topic}, List {listeners} > */ - public Map> notifyList = new HashMap>(); + protected Map> notifyList = new HashMap>(); List listeners = new ArrayList(); @@ -87,8 +93,8 @@ public void setAutoClean(boolean autoClean) { this.autoClean = autoClean; } - public Outbox(NameProvider myService) { - this.myService = myService; + public Outbox(String myService) { + this.name = myService; } public Set getAttached(String publishingPoint) { @@ -135,7 +141,7 @@ public void add(Message msg) { if (msgBox.size() > maxQueue) { // log.warn("{} outbox BUFFER OVERRUN size {} Dropping message to // {}.{}", myService.getName(), msgBox.size(), msg.name, msg.method); - log.warn("{} outbox BUFFER OVERRUN size {} Dropping message to {}", myService.getName(), msgBox.size(), msg); + log.warn("{} outbox BUFFER OVERRUN size {} Dropping message to {}", name, msgBox.size(), msg); } msgBox.addFirst(msg); @@ -210,7 +216,10 @@ public void run() { MRLListener listener = subList.get(i); msg.setName(listener.callbackName); msg.method = listener.callbackMethod; - send(msg); + + if (!isFiltered(msg)) { + send(msg); + } // must make new for internal queues // otherwise you'll change the name on @@ -223,9 +232,25 @@ public void run() { } continue; } - } // while (isRunning) } + + public FilterInterface addFilter(String name, String method, FilterInterface filter) { + return filters.put(String.format("%s.%s", CodecUtils.getFullName(name), method), filter); + } + + public FilterInterface removeFilter(String name, String method) { + return filters.remove(String.format("%s.%s", CodecUtils.getFullName(name), method)); + } + + public boolean isFiltered(Message msg) { + String fullname = CodecUtils.getFullName(msg.name); + if (filters.size() == 0 || !filters.containsKey(String.format("%s.%s", fullname, msg.method))) { + return false; + } else { + return filters.get(String.format("%s.%s", fullname, msg.method)).filter(msg); + } + } public int size() { return msgBox.size(); @@ -233,7 +258,7 @@ public int size() { public void start() { for (int i = outboxThreadPool.size(); i < initialThreadCount; ++i) { - Thread t = new Thread(this, myService.getName() + "_outbox_" + i); + Thread t = new Thread(this, name + "_outbox_" + i); outboxThreadPool.add(t); t.start(); } @@ -351,4 +376,10 @@ synchronized public void detach(String service) { } } + public Map> getNotifyList() { + return notifyList; + } + + + } diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index b6080d2c32..08534f2081 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -67,7 +67,6 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.service.Runtime; -import org.myrobotlab.service.config.RuntimeConfig; import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.config.ServiceConfig.Listener; import org.myrobotlab.service.data.Locale; @@ -144,7 +143,7 @@ public abstract class Service implements Runnable, Seri transient protected Inbox inbox = null; - transient protected Outbox outbox = null; + protected Outbox outbox = null; protected String serviceVersion = null; @@ -194,6 +193,7 @@ public abstract class Service implements Runnable, Seri * a definition. However, since serialization will not process statics - we are making * it a member variable */ + // FIXME - this should be a map protected Map interfaceSet; /** @@ -663,7 +663,7 @@ public Service(String reservedKey, String inId) { loadLocalizations(); this.inbox = new Inbox(getFullName()); - this.outbox = new Outbox(this); + this.outbox = new Outbox(getFullName()); File versionFile = new File(getResourceDir() + fs + "version.txt"); if (versionFile.exists()) { @@ -2786,6 +2786,13 @@ public void setPeerName(String key, String fullName) { // should we also make or update a config file - if the config path is set? info("updated %s name to %s", oldName, peer.name); } + + /** + * get all the subscriptions to this service + */ + public Map> getNotifyList(){ + return getOutbox().getNotifyList(); + } /** * Update a peer's type. First its done in the current Plan, and it will also diff --git a/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java b/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java index 18334da95e..aad5e3a823 100644 --- a/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java +++ b/src/main/java/org/myrobotlab/framework/interfaces/ServiceInterface.java @@ -8,6 +8,7 @@ import org.myrobotlab.framework.Inbox; import org.myrobotlab.framework.MRLListener; +import org.myrobotlab.framework.MethodCache; import org.myrobotlab.framework.MethodEntry; import org.myrobotlab.framework.Outbox; import org.myrobotlab.framework.Peer; @@ -135,9 +136,13 @@ public interface ServiceInterface extends ServiceQueue, LoggingSink, NameTypePro /** * reflectively sets a part of config - * - * @param fieldname - the name of the config field - * @param value - the value + * + * @param fieldname + * @param value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws NoSuchFieldException + * @throws SecurityException */ void setConfigValue(String fieldname, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException; @@ -216,5 +221,9 @@ public interface ServiceInterface extends ServiceQueue, LoggingSink, NameTypePro * @return */ public ServiceConfig addConfigListeners(ServiceConfig config); - + + /** + * get all the subscriptions to this service + */ + public Map> getNotifyList(); } diff --git a/src/main/java/org/myrobotlab/service/LocalSpeech.java b/src/main/java/org/myrobotlab/service/LocalSpeech.java index 9496bf3fb7..292198f3f6 100644 --- a/src/main/java/org/myrobotlab/service/LocalSpeech.java +++ b/src/main/java/org/myrobotlab/service/LocalSpeech.java @@ -132,7 +132,7 @@ public AudioData generateAudioData(AudioData audioData, String toSpeak) throws I args.add("$speak.SelectVoice('" + getVoice().getVoiceProvider().toString() + "');"); args.add("$speak.SetOutputToWaveFile('" + localFileName + "');"); args.add("$speak.speak('" + toSpeak + "')"); - String ret = Runtime.execute("powershell.exe", args, null, null, null); + String ret = Runtime.execute("powershell.exe", args, null, null, true); log.info("powershell returned : {}", ret); @@ -206,8 +206,10 @@ public void loadVoices() { String voicesText = null; + // FIXME this is not right - it should be based on speechType not OS + // speechType should be "set" based on OS and user preference if (platform.isWindows()) { - + try { List args = new ArrayList<>(); @@ -219,7 +221,7 @@ public void loadVoices() { args.add("Select-Object -Property * | "); // args.add("Select-Object -Property Culture, Name, Gender, Age"); args.add("ConvertTo-Json "); - voicesText = Runtime.execute("powershell.exe", args, null, null, null); + voicesText = Runtime.execute("powershell.exe", args, null, null, true); // voicesText = Runtime.execute("cmd.exe", "/c", "\"\"" + ttsPath + "\"" // + " -V" + "\""); @@ -269,9 +271,11 @@ public void loadVoices() { addVoice(matcher.group(1).toLowerCase(), "male", matcher.group(2), matcher.group(1).toLowerCase()); } } - } else if (platform.isLinux()) { - addVoice("Linus", "male", "en-US", "festival"); } + // let apply config add and set the voices +// else if (platform.isLinux()) { +// addVoice("Linus", "male", "en-US", "festival"); +// } } public void removeExt(boolean b) { @@ -282,6 +286,11 @@ public void removeExt(boolean b) { * @return setEspeak sets the Linux tts to espeak template */ public boolean setEspeak() { + if (!Runtime.getPlatform().isLinux()) { + error("espeak only supported on Linux"); + return false; + } + LocalSpeechConfig c = (LocalSpeechConfig) config; c.speechType = "Espeak"; voices.clear(); @@ -296,6 +305,11 @@ public boolean setEspeak() { * @return setFestival sets the Linux tts to festival template */ public boolean setFestival() { + if (!Runtime.getPlatform().isLinux()) { + error("festival only supported on Linux"); + return false; + } + LocalSpeechConfig c = (LocalSpeechConfig) config; voices.clear(); addVoice("Linus", "male", "en-US", "festival"); @@ -303,10 +317,6 @@ public boolean setFestival() { removeExt(false); setTtsHack(false); setTtsCommand("echo \"{text}\" | text2wave -o {filename}"); - if (!Runtime.getPlatform().isLinux()) { - error("festival only supported on Linux"); - return false; - } return true; } @@ -316,6 +326,11 @@ public boolean setFestival() { * @return true if successfully switched */ public boolean setPico2Wav() { + if (!Runtime.getPlatform().isLinux()) { + error("pico2wave only supported on Linux"); + return false; + } + LocalSpeechConfig c = (LocalSpeechConfig) config; c.speechType = "Pico2Wav"; removeExt(false); @@ -328,12 +343,13 @@ public boolean setPico2Wav() { addVoice("es-ES", "female", "es-ES", "pico2wav"); addVoice("fr-FR", "female", "fr-FR", "pico2wav"); addVoice("it-IT", "female", "it-IT", "pico2wav"); + + if (voice == null) { + setVoice(getLocale().getTag()); + } setTtsCommand("pico2wave -l {voice_name} -w {filename} \"{text}\" "); - if (!Runtime.getPlatform().isLinux()) { - error("pico2wave only supported on Linux"); - return false; - } + broadcastState(); return true; } @@ -477,6 +493,26 @@ public void setTtsHack(boolean b) { public void setTtsPath(String ttsPath) { this.ttsPath = ttsPath; } + + public boolean isExecutableAvailable(String executableName) { + ProcessBuilder processBuilder = new ProcessBuilder(); + String command = ""; + boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows"); + if (isWindows) { + command = "where " + executableName; + } else { + command = "which " + executableName; + } + processBuilder.command("sh", "-c", command); + try { + Process process = processBuilder.start(); + process.waitFor(); + return process.exitValue() == 0; + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + return false; + } +} public LocalSpeechConfig apply(LocalSpeechConfig config) { super.apply(config); @@ -489,7 +525,11 @@ public LocalSpeechConfig apply(LocalSpeechConfig config) { } else if (platform.isMac()) { setSay(); } else if (platform.isLinux()) { - setFestival(); + if (isExecutableAvailable("pico2wave")) { + setPico2Wav(); + } else { + setFestival(); + } } else { error("%s unknown platform %s", getName(), platform.getOS()); } @@ -537,7 +577,7 @@ public static void main(String[] args) { arguments.add("Add-Type -AssemblyName System.Speech;"); arguments.add("$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;"); arguments.add("$speak.speak('HELLO !!!!');"); - Runtime.execute("powershell.exe", arguments, null, null, null); + Runtime.execute("powershell.exe", arguments, null, null, true); // log.info(ret); mouth.speakBlocking("hello my name is sam, sam i am yet again, how \"are you? do you 'live in a zoo too? "); diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 5842a6b3cd..24682c4dfb 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.Random; import java.util.Set; import java.util.TimeZone; @@ -95,6 +96,7 @@ import org.myrobotlab.service.meta.abstracts.MetaData; import org.myrobotlab.string.StringUtil; import org.slf4j.Logger; +import org.yaml.snakeyaml.constructor.ConstructorException; import picocli.CommandLine; @@ -3434,7 +3436,7 @@ static public void removeAllSubscriptions() { for (ServiceInterface si : getLocalServices().values()) { List nlks = si.getNotifyListKeySet(); for (int i = 0; i < nlks.size(); ++i) { - si.getOutbox().notifyList.clear(); + si.getNotifyList().clear(); } } } @@ -3505,7 +3507,7 @@ static public String execute(String... args) { } } - return execute(program, list, null, null, null); + return execute(program, list, null, null, true); } /** @@ -3527,79 +3529,69 @@ static public String execute(String... args) { * Whether this method blocks for the program to execute * @return The programs stderr and stdout output */ - static public String execute(String program, List args, String workingDir, Map additionalEnv, Boolean block) { - + static public String execute(String program, List args, String workingDir, Map additionalEnv, boolean block) { log.info("execToString(\"{} {}\")", program, args); - ArrayList command = new ArrayList(); + List command = new ArrayList<>(); command.add(program); if (args != null) { - for (String arg : args) { - command.add(arg); - } + command.addAll(args); } - Integer exitValue = null; - ProcessBuilder builder = new ProcessBuilder(command); + if (workingDir != null) { + builder.directory(new File(workingDir)); + } Map environment = builder.environment(); if (additionalEnv != null) { - environment.putAll(additionalEnv); + environment.putAll(additionalEnv); } - StringBuilder outputBuilder; - - try { - Process handle = builder.start(); - - InputStream stdErr = handle.getErrorStream(); - InputStream stdOut = handle.getInputStream(); - // TODO: we likely don't need this - // OutputStream stdIn = handle.getOutputStream(); + StringBuilder outputBuilder = new StringBuilder(); - outputBuilder = new StringBuilder(); - byte[] buff = new byte[32768]; - - // TODO: should we read both of these streams? - // if we break out of the first loop is the process terminated? - - // read stdout - for (int n; (n = stdOut.read(buff)) != -1;) { - outputBuilder.append(new String(buff, 0, n)); - } - - // read stderr - for (int n; (n = stdErr.read(buff)) != -1;) { - outputBuilder.append(new String(buff, 0, n)); - } - - stdOut.close(); - stdErr.close(); + try { + Process handle = builder.start(); - // TODO: stdin if we use it. - // stdIn.close(); + InputStream stdErr = handle.getErrorStream(); + InputStream stdOut = handle.getInputStream(); - // the process should be closed by now? + // Read the output streams in separate threads to avoid potential blocking + Thread stdErrThread = new Thread(() -> readStream(stdErr, outputBuilder)); + stdErrThread.start(); - handle.waitFor(); + Thread stdOutThread = new Thread(() -> readStream(stdOut, outputBuilder)); + stdOutThread.start(); - handle.destroy(); + if (block) { + int exitValue = handle.waitFor(); + outputBuilder.append("Exit Value: ").append(exitValue); + log.info("Command exited with exit value: {}", exitValue); + } else { + log.info("Command started"); + } - exitValue = handle.exitValue(); - // print the output from the command - // TODO replace with logging calls - System.out.println(outputBuilder.toString()); - System.out.println("Exit Value : " + exitValue); - outputBuilder.append("Exit Value : " + exitValue); + return outputBuilder.toString(); + } catch (IOException e) { + log.error("Error executing command", e); + return e.getMessage(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Command execution interrupted", e); + return e.getMessage(); + } +} - return outputBuilder.toString(); - } catch (Exception e) { - log.error("execute threw", e); - exitValue = 5; - return e.getMessage(); +private static void readStream(InputStream inputStream, StringBuilder outputBuilder) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + outputBuilder.append(line).append(System.lineSeparator()); + } + } catch (IOException e) { + log.error("Error reading process output", e); } - } +} /** * Get the current battery level of the computer this MRL instance is running @@ -3956,7 +3948,7 @@ public List getServiceTypes() { } return filteredTypes; } - + /** * Register a connection route from one instance to this one. * @@ -4217,10 +4209,7 @@ public Gateway getGatway(String remoteId) { * name */ static public String getFullName(String shortname) { - if (shortname == null) { - return null; - } - if (shortname.contains("@")) { + if (shortname == null || shortname.contains("@")) { // already long form return shortname; } @@ -4852,6 +4841,8 @@ public ServiceConfig readServiceConfig(String configName, String name) { if (check.exists()) { try { sc = CodecUtils.readServiceConfig(filename); + } catch (ConstructorException e) { + error("%s invalid %s %s. Please remove it from the file.", name, filename, e.getCause().getMessage()); } catch (IOException e) { error(e); } From 168c1eb4d7a9cb9de698cfd0692681a518c27e29 Mon Sep 17 00:00:00 2001 From: GroG Date: Sun, 13 Aug 2023 15:53:33 -0700 Subject: [PATCH 40/54] BoofCV (#1332) * boofcv updates * removed boofcv in opencv --- pom.xml | 968 +++++++++--------- .../org/myrobotlab/boofcv/BoofCVFilter.java | 52 + .../boofcv/BoofCVFilterTrackerObjectQuad.java | 68 ++ .../org/myrobotlab/boofcv/BoofCvData.java | 31 - .../boofcv/CaptureCalibrationImagesApp.java | 187 ---- .../boofcv/CreateRgbPointCloudFileApp.java | 80 -- .../boofcv/DisplayKinectPointCloudApp.java | 88 -- .../boofcv/ExampleDepthPointCloud.java | 100 -- .../boofcv/ExampleVisualOdometryDepth.java | 216 ---- .../boofcv/IntrinsicToDepthParameters.java | 53 - .../myrobotlab/boofcv/LogKinectDataApp.java | 131 --- .../boofcv/OpenKinectExampleParam.java | 30 - .../myrobotlab/boofcv/OpenKinectOdometry.java | 451 -------- .../boofcv/OpenKinectPointCloud.java | 225 ---- .../boofcv/OpenKinectStreamingTest.java | 141 --- .../boofcv/OverlayRgbDepthStreamsApp.java | 98 -- .../boofcv/PlaybackKinectLogApp.java | 95 -- .../interfaces => cv}/ComputerVision.java | 3 +- .../opencv/OpenCVFilterKinectPointCloud.java | 310 ------ .../java/org/myrobotlab/service/BoofCV.java | 431 ++++++++ .../java/org/myrobotlab/service/BoofCv.java | 50 - .../java/org/myrobotlab/service/Tracking.java | 3 +- .../abstracts/AbstractComputerVision.java | 2 +- .../service/config/BoofCVConfig.java | 12 + .../myrobotlab/service/meta/BoofCVMeta.java | 23 + .../myrobotlab/service/meta/BoofCvMeta.java | 26 - .../resource/{BoofCv.png => BoofCV.png} | Bin .../{BoofCv/BoofCv.py => BoofCV/BoofCV.py} | 0 .../resource/BoofCv/basket_depth.png | Bin 94310 -> 0 bytes .../resources/resource/BoofCv/basket_rgb.png | Bin 579028 -> 0 bytes src/main/resources/resource/BoofCv/boofcv.yml | 4 - .../resources/resource/BoofCv/intrinsic.yaml | 19 - .../resource/BoofCv/visualdepth.yaml | 20 - .../WebGui/app/service/js/BoofCVGui.js | 243 +++++ .../WebGui/app/service/views/BoofCVGui.html | 246 +++++ 35 files changed, 1556 insertions(+), 2850 deletions(-) create mode 100644 src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java create mode 100644 src/main/java/org/myrobotlab/boofcv/BoofCVFilterTrackerObjectQuad.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/BoofCvData.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/CaptureCalibrationImagesApp.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/CreateRgbPointCloudFileApp.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/DisplayKinectPointCloudApp.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/ExampleDepthPointCloud.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/ExampleVisualOdometryDepth.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/IntrinsicToDepthParameters.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/LogKinectDataApp.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/OpenKinectExampleParam.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/OpenKinectOdometry.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/OpenKinectPointCloud.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/OpenKinectStreamingTest.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/OverlayRgbDepthStreamsApp.java delete mode 100644 src/main/java/org/myrobotlab/boofcv/PlaybackKinectLogApp.java rename src/main/java/org/myrobotlab/{service/interfaces => cv}/ComputerVision.java (81%) delete mode 100644 src/main/java/org/myrobotlab/opencv/OpenCVFilterKinectPointCloud.java create mode 100644 src/main/java/org/myrobotlab/service/BoofCV.java delete mode 100644 src/main/java/org/myrobotlab/service/BoofCv.java create mode 100644 src/main/java/org/myrobotlab/service/config/BoofCVConfig.java create mode 100644 src/main/java/org/myrobotlab/service/meta/BoofCVMeta.java delete mode 100644 src/main/java/org/myrobotlab/service/meta/BoofCvMeta.java rename src/main/resources/resource/{BoofCv.png => BoofCV.png} (100%) rename src/main/resources/resource/{BoofCv/BoofCv.py => BoofCV/BoofCV.py} (100%) delete mode 100644 src/main/resources/resource/BoofCv/basket_depth.png delete mode 100644 src/main/resources/resource/BoofCv/basket_rgb.png delete mode 100644 src/main/resources/resource/BoofCv/boofcv.yml delete mode 100644 src/main/resources/resource/BoofCv/intrinsic.yaml delete mode 100644 src/main/resources/resource/BoofCv/visualdepth.yaml create mode 100644 src/main/resources/resource/WebGui/app/service/js/BoofCVGui.js create mode 100644 src/main/resources/resource/WebGui/app/service/views/BoofCVGui.html diff --git a/pom.xml b/pom.xml index 3dfa83b437..f3502dffce 100644 --- a/pom.xml +++ b/pom.xml @@ -1,102 +1,102 @@ - - - 4.0.0 - org.myrobotlab - mrl - 0.0.1-SNAPSHOT - MyRobotLab - Open Source Creative Machine Control - - - false - - - - 1.1. - - ${maven.build.timestamp} - yyyyMMddHHmm - ${timestamp} - ${version.prefix}${build.number} - ${git.branch} - ${NODE_NAME} - ${NODE_LABELS} - - - - 11 - 11 - UTF-8 - - - + + + 4.0.0 + org.myrobotlab + mrl + 0.0.1-SNAPSHOT + MyRobotLab + Open Source Creative Machine Control + + + false + + + + 1.1. + + ${maven.build.timestamp} + yyyyMMddHHmm + ${timestamp} + ${version.prefix}${build.number} + ${git.branch} + ${NODE_NAME} + ${NODE_LABELS} + + + + 11 + 11 + UTF-8 + + + @@ -135,9 +135,9 @@ https://m2.dv8tion.net/releases - - - + + + javazoom @@ -163,26 +163,14 @@ - + org.boofcv - boofcv-core - 0.31 + boofcv-all + 0.40.1 provided - - org.boofcv - boofcv-swing - 0.31 - provided - - - org.boofcv - boofcv-openkinect - 0.31 - provided - - + @@ -1746,375 +1734,375 @@ - - - org.mockito - mockito-core - 3.12.4 - test - - - - - - - false - src/main/resources - - - false - src/main/java - - ** - - - **/*.java - - - - - - false - src/test/resources - - - false - src/test/java - - ** - - - **/*.java - - - - src/main/resources - ${project.basedir} - - - - - - - - org.codehaus.mojo - properties-maven-plugin - 1.0.0 - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.1.0 - - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - no-duplicate-declared-dependencies - - enforce - - - - - - - - - - - - org.codehaus.mojo - properties-maven-plugin - - - initialize - - read-project-properties - - - - build.properties - - - - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.1.0 - - - package - - shade - - - myrobotlab - - true - myrobotlab-full - false - - - - - org.myrobotlab.service.Runtime - ${version} - ${version} - - ${build.number} - ${maven.build.timestamp} - ${agent.name} - ${user.name} - - - ${git.tags} - ${git.branch} - ${git.dirty} - ${git.remote.origin.url} - ${git.commit.id} - ${git.commit.id.abbrev} - ${git.commit.id.full} - ${git.commit.id.describe} - ${git.commit.id.describe-short} - ${git.commit.user.name} - ${git.commit.user.email} - - ${git.commit.time} - ${git.closest.tag.name} - ${git.closest.tag.commit.count} - ${git.build.user.name} - ${git.build.user.email} - ${git.build.time} - ${git.build.version} - - - - - - - *:* - - module-info.class - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - assembly.xml - - myrobotlab - false - - - - trigger-assembly - package - - single - - - - - - - true - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 11 - 11 - true - true - -parameters - - - - - org.apache.maven.plugins - maven-resources-plugin - 2.4.3 - - - - pl.project13.maven - git-commit-id-plugin - 4.9.10 - - - initialize - get-the-git-infos - - revision - - - - - ${project.basedir}/.git - git - false - true - ${project.build.outputDirectory}/git.properties - - - false - false - -dirty - - - - - - maven-surefire-plugin - org.apache.maven.plugins - 2.22.2 - - -Djava.library.path=libraries/native -Djna.library.path=libraries/native - - **/*Test.java - - - **/integration/* - - - - - - - - org.apache.maven.plugins - maven-clean-plugin - 2.3 - - - - data/.myrobotlab - false - - - libraries - - ** - - false - - - data - - ** - - - - resource - - ** - - - - src/main/resources/resource/framework - - **/serviceData.json - - false - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.22.2 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.0.1 - - - - - myrobotlab - http://myrobotlab.org - - - github - https://github.com/MyRobotLab/myrobotlab/issues - - + + + org.mockito + mockito-core + 3.12.4 + test + + + + + + + false + src/main/resources + + + false + src/main/java + + ** + + + **/*.java + + + + + + false + src/test/resources + + + false + src/test/java + + ** + + + **/*.java + + + + src/main/resources + ${project.basedir} + + + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.1.0 + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + no-duplicate-declared-dependencies + + enforce + + + + + + + + + + + + org.codehaus.mojo + properties-maven-plugin + + + initialize + + read-project-properties + + + + build.properties + + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + myrobotlab + + true + myrobotlab-full + false + + + + + org.myrobotlab.service.Runtime + ${version} + ${version} + + ${build.number} + ${maven.build.timestamp} + ${agent.name} + ${user.name} + + + ${git.tags} + ${git.branch} + ${git.dirty} + ${git.remote.origin.url} + ${git.commit.id} + ${git.commit.id.abbrev} + ${git.commit.id.full} + ${git.commit.id.describe} + ${git.commit.id.describe-short} + ${git.commit.user.name} + ${git.commit.user.email} + + ${git.commit.time} + ${git.closest.tag.name} + ${git.closest.tag.commit.count} + ${git.build.user.name} + ${git.build.user.email} + ${git.build.time} + ${git.build.version} + + + + + + + *:* + + module-info.class + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assembly.xml + + myrobotlab + false + + + + trigger-assembly + package + + single + + + + + + + true + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 11 + 11 + true + true + -parameters + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.4.3 + + + + pl.project13.maven + git-commit-id-plugin + 4.9.10 + + + initialize + get-the-git-infos + + revision + + + + + ${project.basedir}/.git + git + false + true + ${project.build.outputDirectory}/git.properties + + + false + false + -dirty + + + + + + maven-surefire-plugin + org.apache.maven.plugins + 2.22.2 + + -Djava.library.path=libraries/native -Djna.library.path=libraries/native + + **/*Test.java + + + **/integration/* + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 2.3 + + + + data/.myrobotlab + false + + + libraries + + ** + + false + + + data + + ** + + + + resource + + ** + + + + src/main/resources/resource/framework + + **/serviceData.json + + false + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.22.2 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.1 + + + + + myrobotlab + http://myrobotlab.org + + + github + https://github.com/MyRobotLab/myrobotlab/issues + + diff --git a/src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java b/src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java new file mode 100644 index 0000000000..3e4f09b5a5 --- /dev/null +++ b/src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java @@ -0,0 +1,52 @@ +package org.myrobotlab.boofcv; + +import org.myrobotlab.cv.CvFilter; +import org.myrobotlab.service.BoofCV; + +import boofcv.struct.image.ImageBase; + +public abstract class BoofCVFilter implements CvFilter { + + protected String name; + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + protected boolean enabled = true; + transient BoofCV boofcv = null; + + public BoofCVFilter(String name) { + this.name = name; + } + + @Override + public void disable() { + enabled = false; + } + + @Override + public void enable() { + enabled = true; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + + public void setBoofCV(BoofCV boofcv) { + this.boofcv = boofcv; + } + + public abstract ImageBase process(ImageBase image) throws InterruptedException; + + // override if necessary + public void release() { + } + +} diff --git a/src/main/java/org/myrobotlab/boofcv/BoofCVFilterTrackerObjectQuad.java b/src/main/java/org/myrobotlab/boofcv/BoofCVFilterTrackerObjectQuad.java new file mode 100644 index 0000000000..b75014235d --- /dev/null +++ b/src/main/java/org/myrobotlab/boofcv/BoofCVFilterTrackerObjectQuad.java @@ -0,0 +1,68 @@ +package org.myrobotlab.boofcv; + +import java.awt.Dimension; +import java.awt.image.BufferedImage; + +import boofcv.abst.tracker.TrackerObjectQuad; +import boofcv.factory.tracker.FactoryTrackerObjectQuad; +import boofcv.gui.image.ShowImages; +import boofcv.gui.tracker.TrackerObjectQuadPanel; +import boofcv.struct.image.GrayU8; +import boofcv.struct.image.ImageBase; +import georegression.struct.shapes.Quadrilateral_F64; + +public class BoofCVFilterTrackerObjectQuad extends BoofCVFilter { + + protected transient TrackerObjectQuad tracker = null; + + protected Quadrilateral_F64 location = null; + + protected boolean visible = true; + + protected boolean trackerInitialized = false; + + protected transient TrackerObjectQuadPanel gui = null; + + public BoofCVFilterTrackerObjectQuad(String name) { + super(name); + } + + @Override + public ImageBase process(ImageBase frame) throws InterruptedException { + + if (!trackerInitialized && location != null) { + tracker = FactoryTrackerObjectQuad.circulant(null, GrayU8.class); + tracker.initialize(frame, location); + trackerInitialized = true; + } + + // FIXME - probably replace BoofCV servie native gui with gui supplied by + // filter + // + if (gui == null) { + gui = new TrackerObjectQuadPanel(null); + gui.setPreferredSize(new Dimension(frame.getWidth(), frame.getHeight())); + gui.setImageUI((BufferedImage) boofcv.getGuiImage()); + gui.setTarget(location, true); + ShowImages.showWindow(gui, "Tracking Results", true); + } + + if (location != null) { + visible = tracker.process(frame, location); + + if (gui != null) { + gui.setImageUI((BufferedImage)boofcv.getGuiImage()); + gui.setTarget(location, visible); + gui.repaint(); + } + } + + return frame; + } + + public void setLocation(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) { + location = new Quadrilateral_F64(x0, y0, x1, y1, x2, y2, x3, y3); + trackerInitialized = false; + } + +} diff --git a/src/main/java/org/myrobotlab/boofcv/BoofCvData.java b/src/main/java/org/myrobotlab/boofcv/BoofCvData.java deleted file mode 100644 index f29dc4e419..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/BoofCvData.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.myrobotlab.boofcv; - -import java.util.List; -import java.util.Set; - -import org.myrobotlab.cv.CvData; -import org.myrobotlab.math.geometry.PointCloud; - -public class BoofCvData extends CvData { - - private static final long serialVersionUID = 1L; - - @Override - public Set getKeySet() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List getPointCloudList() { - // TODO Auto-generated method stub - return null; - } - - @Override - public PointCloud getPointCloud() { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/src/main/java/org/myrobotlab/boofcv/CaptureCalibrationImagesApp.java b/src/main/java/org/myrobotlab/boofcv/CaptureCalibrationImagesApp.java deleted file mode 100644 index 6bdc9d5682..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/CaptureCalibrationImagesApp.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; - -import org.ddogleg.struct.GrowQueue_I8; -import org.openkinect.freenect.Context; -import org.openkinect.freenect.Device; -import org.openkinect.freenect.Freenect; -import org.openkinect.freenect.Resolution; - -import com.sun.jna.NativeLibrary; - -import boofcv.gui.image.ImagePanel; -import boofcv.gui.image.ShowImages; -import boofcv.io.image.ConvertBufferedImage; -import boofcv.io.image.UtilImageIO; -import boofcv.misc.BoofMiscOps; -import boofcv.openkinect.StreamOpenKinectRgbDepth; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; - -/** - * Displays RGB image from the kinect and then pauses after a set period of - * time. At which point the user can press 'y' or 'n' to indicate yes or no for - * saving the RGB and depth images. Useful when collecting calibration images - * - * @author Peter Abeles - */ -public class CaptureCalibrationImagesApp implements KeyListener, StreamOpenKinectRgbDepth.Listener { - { - // be sure to set OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY to the - // location of your shared library! - NativeLibrary.addSearchPath("freenect", OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY); - } - - String directory; - int period; - Resolution resolution = Resolution.MEDIUM; - - BufferedImage buffRgb; - int frameNumber; - - GrowQueue_I8 buffer = new GrowQueue_I8(1); - - ImagePanel gui; - - Planar savedRgb; - GrayU16 savedDepth; - - volatile boolean updateDisplay; - volatile boolean savedImages; - volatile int userChoice; - - String text; - long timeText; - - public CaptureCalibrationImagesApp(int period, String directory) { - this.period = period; - this.directory = directory; - } - - public void process() throws IOException { - - // make sure there is a "log" directory - new File("log").mkdir(); - - int w = UtilOpenKinect.getWidth(resolution); - int h = UtilOpenKinect.getHeight(resolution); - - buffRgb = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); - - savedRgb = new Planar<>(GrayU8.class, w, h, 3); - savedDepth = new GrayU16(w, h); - - gui = ShowImages.showWindow(buffRgb, "Kinect RGB"); - gui.addKeyListener(this); - gui.requestFocus(); - - StreamOpenKinectRgbDepth stream = new StreamOpenKinectRgbDepth(); - Context kinect = Freenect.createContext(); - - if (kinect.numDevices() < 0) - throw new RuntimeException("No kinect found!"); - - Device device = kinect.openDevice(0); - - stream.start(device, resolution, this); - - long targetTime = System.currentTimeMillis() + period; - updateDisplay = true; - while (true) { - BoofMiscOps.pause(100); - - if (targetTime < System.currentTimeMillis()) { - userChoice = -1; - savedImages = false; - updateDisplay = false; - while (true) { - if (savedImages && userChoice != -1) { - if (userChoice == 1) { - UtilImageIO.savePPM(savedRgb, String.format(directory + "rgb%07d.ppm", frameNumber), buffer); - UtilOpenKinect.saveDepth(savedDepth, String.format(directory + "depth%07d.depth", frameNumber), buffer); - frameNumber++; - text = "Image Saved!"; - } else { - text = "Image Discarded!"; - } - timeText = System.currentTimeMillis() + 500; - updateDisplay = true; - targetTime = System.currentTimeMillis() + period; - break; - } - BoofMiscOps.pause(50); - } - } - } - } - - @Override - public void processKinect(Planar rgb, GrayU16 depth, long timeRgb, long timeDepth) { - - if (updateDisplay) { - ConvertBufferedImage.convertTo_U8(rgb, buffRgb, true); - - if (timeText >= System.currentTimeMillis()) { - Graphics2D g2 = buffRgb.createGraphics(); - g2.setFont(new Font(Font.MONOSPACED, Font.BOLD, 30)); - g2.setColor(Color.RED); - g2.drawString(text, rgb.width / 2 - 100, rgb.height / 2); - } - - gui.repaint(); - } else if (!savedImages) { - savedRgb.setTo(rgb); - savedDepth.setTo(depth); - savedImages = true; - } - } - - public static void main(String args[]) throws IOException { - CaptureCalibrationImagesApp app = new CaptureCalibrationImagesApp(3000, "log/"); - app.process(); - } - - @Override - public void keyTyped(KeyEvent e) { - if (e.getKeyChar() == 'y') - userChoice = 1; - else - userChoice = 0; - } - - @Override - public void keyPressed(KeyEvent e) { - } - - @Override - public void keyReleased(KeyEvent e) { - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/CreateRgbPointCloudFileApp.java b/src/main/java/org/myrobotlab/boofcv/CreateRgbPointCloudFileApp.java deleted file mode 100644 index 9cdde9d86b..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/CreateRgbPointCloudFileApp.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.io.DataOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -import org.ddogleg.struct.FastQueue; - -import boofcv.alg.depth.VisualDepthOps; -import boofcv.io.calibration.CalibrationIO; -import boofcv.io.image.UtilImageIO; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.FastQueueArray_I32; -import boofcv.struct.calib.CameraPinholeRadial; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; -import georegression.struct.point.Point3D_F64; - -/** - * Loads kinect observations and saves a point cloud with rgb information to a - * file in a simple CSV format - * - * @author Peter Abeles - */ -public class CreateRgbPointCloudFileApp { - public static void main(String args[]) throws IOException { - String baseDir = "log/"; - - String nameRgb = baseDir + "rgb0000000.ppm"; - String nameDepth = baseDir + "depth0000000.depth"; - String nameCalib = baseDir + "intrinsic.yaml"; - - CameraPinholeRadial param = CalibrationIO.load(nameCalib); - - GrayU16 depth = new GrayU16(1, 1); - Planar rgb = new Planar<>(GrayU8.class, 1, 1, 3); - - UtilImageIO.loadPPM_U8(nameRgb, rgb, null); - UtilOpenKinect.parseDepth(nameDepth, depth, null); - - FastQueue cloud = new FastQueue(Point3D_F64.class, true); - FastQueueArray_I32 cloudColor = new FastQueueArray_I32(3); - - VisualDepthOps.depthTo3D(param, rgb, depth, cloud, cloudColor); - - DataOutputStream file = new DataOutputStream(new FileOutputStream("kinect_pointcloud.txt")); - - file.write("# Kinect RGB Point cloud. Units: millimeters. Format: X Y Z R G B\n".getBytes()); - - for (int i = 0; i < cloud.size; i++) { - Point3D_F64 p = cloud.get(i); - int[] color = cloudColor.get(i); - - String line = String.format("%.10f %.10f %.10f %d %d %d\n", p.x, p.y, p.z, color[0], color[1], color[2]); - file.write(line.getBytes()); - } - file.close(); - - System.out.println("Total points = " + cloud.size); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/DisplayKinectPointCloudApp.java b/src/main/java/org/myrobotlab/boofcv/DisplayKinectPointCloudApp.java deleted file mode 100644 index 8f394ac13d..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/DisplayKinectPointCloudApp.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2011-2018, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.Dimension; -import java.io.File; -import java.io.IOException; - -import org.ddogleg.struct.FastQueue; -import org.myrobotlab.framework.Service; -import org.myrobotlab.service.BoofCv; - -import boofcv.alg.depth.VisualDepthOps; -import boofcv.alg.geo.PerspectiveOps; -import boofcv.gui.image.ShowImages; -import boofcv.io.calibration.CalibrationIO; -import boofcv.io.image.UtilImageIO; -import boofcv.struct.FastQueueArray_I32; -import boofcv.struct.calib.CameraPinholeRadial; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.ImageType; -import boofcv.struct.image.Planar; -import boofcv.visualize.PointCloudViewer; -import boofcv.visualize.VisualizeData; -import georegression.struct.point.Point3D_F64; - -/** - * Loads kinect data from two files and displays the cloud in a 3D simple - * viewer. - * - * @author Peter Abeles - */ -public class DisplayKinectPointCloudApp { - - public static void main(String args[]) throws IOException { - // String baseDir = UtilIO.pathExample("kinect/basket"); - String baseDir = Service.getResourceDir(BoofCv.class); - String nameRgb = "basket_rgb.png"; - String nameDepth = "basket_depth.png"; - String nameCalib = "intrinsic.yaml"; - - CameraPinholeRadial param = CalibrationIO.load(new File(baseDir, nameCalib)); - - GrayU16 depth = UtilImageIO.loadImage(new File(baseDir, nameDepth), false, ImageType.single(GrayU16.class)); - Planar rgb = UtilImageIO.loadImage(new File(baseDir, nameRgb), true, ImageType.pl(3, GrayU8.class)); - - FastQueue cloud = new FastQueue(Point3D_F64.class, true); - FastQueueArray_I32 cloudColor = new FastQueueArray_I32(3); - - VisualDepthOps.depthTo3D(param, rgb, depth, cloud, cloudColor); - - PointCloudViewer viewer = VisualizeData.createPointCloudViewer(); - viewer.setCameraHFov(PerspectiveOps.computeHFov(param)); - viewer.setTranslationStep(10.0); - viewer.getComponent().setPreferredSize(new Dimension(rgb.width, rgb.height)); - - for (int i = 0; i < cloud.size; i++) { - Point3D_F64 p = cloud.get(i); - int[] color = cloudColor.get(i); - int c = (color[0] << 16) | (color[1] << 8) | color[2]; - viewer.addPoint(p.x, p.y, p.z, c); - } - - ShowImages.showWindow(viewer.getComponent(), "Point Cloud", true); - System.out.println("Total points = " + cloud.size); - - // BufferedImage depthOut = VisualizeImageData.disparity(depth, null, 0, - // UtilOpenKinect.FREENECT_DEPTH_MM_MAX_VALUE, 0); - // ShowImages.showWindow(depthOut,"Depth Image", true); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/ExampleDepthPointCloud.java b/src/main/java/org/myrobotlab/boofcv/ExampleDepthPointCloud.java deleted file mode 100644 index a96e8d317a..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/ExampleDepthPointCloud.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.io.IOException; - -import org.ddogleg.struct.FastQueue; -import org.myrobotlab.framework.Service; -import org.myrobotlab.service.BoofCv; - -import boofcv.alg.depth.VisualDepthOps; -import boofcv.alg.geo.PerspectiveOps; -import boofcv.alg.misc.ImageStatistics; -import boofcv.gui.image.ShowImages; -import boofcv.gui.image.VisualizeImageData; -import boofcv.io.calibration.CalibrationIO; -import boofcv.io.image.ConvertBufferedImage; -import boofcv.io.image.UtilImageIO; -import boofcv.struct.FastQueueArray_I32; -import boofcv.struct.calib.VisualDepthParameters; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; -import boofcv.visualize.PointCloudViewer; -import boofcv.visualize.VisualizeData; -import georegression.struct.point.Point3D_F64; - -/** - * Displays RGB image from the kinect and then pauses after a set period of time. At which point the user can press - * 'y' or 'n' to indicate yes or no for saving the RGB and depth images. Useful when collecting calibration images - * - * @author Peter Abeles - */ -/** - * Example of how to create a point cloud from a RGB-D (Kinect) sensor. Data is - * loaded from two files, one for the visual image and one for the depth image. - * - * @author Peter Abeles - */ -public class ExampleDepthPointCloud { - - public static void main(String args[]) throws IOException { - - // String baseDir = "src/main/resources/resource/BoofCv"; - String nameRgb = Service.getResourceDir(BoofCv.class, "basket_rgb.png"); - String nameDepth = Service.getResourceDir(BoofCv.class, "basket_depth.png"); - String nameCalib = Service.getResourceDir(BoofCv.class, "visualdepth.yaml"); - - VisualDepthParameters param = CalibrationIO.load(nameCalib); - - BufferedImage buffered = UtilImageIO.loadImage(nameRgb); - Planar rgb = ConvertBufferedImage.convertFromPlanar(buffered, null, true, GrayU8.class); - GrayU16 depth = ConvertBufferedImage.convertFrom(UtilImageIO.loadImage(nameDepth), null, GrayU16.class); - - FastQueue cloud = new FastQueue<>(Point3D_F64.class, true); - FastQueueArray_I32 cloudColor = new FastQueueArray_I32(3); - - VisualDepthOps.depthTo3D(param.visualParam, rgb, depth, cloud, cloudColor); - - PointCloudViewer viewer = VisualizeData.createPointCloudViewer(); - viewer.setCameraHFov(PerspectiveOps.computeHFov(param.visualParam)); - viewer.setTranslationStep(15); - - for (int i = 0; i < cloud.size; i++) { - Point3D_F64 p = cloud.get(i); - int[] color = cloudColor.get(i); - int c = (color[0] << 16) | (color[1] << 8) | color[2]; - viewer.addPoint(p.x, p.y, p.z, c); - } - viewer.getComponent().setPreferredSize(new Dimension(rgb.width, rgb.height)); - - // ---------- Display depth image - // use the actual max value in the image to maximize its appearance - int maxValue = ImageStatistics.max(depth); - BufferedImage depthOut = VisualizeImageData.disparity(depth, null, 0, maxValue, 0); - ShowImages.showWindow(depthOut, "Depth Image", true); - - // ---------- Display colorized point cloud - ShowImages.showWindow(viewer.getComponent(), "Point Cloud", true); - System.out.println("Total points = " + cloud.size); - } -} \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/boofcv/ExampleVisualOdometryDepth.java b/src/main/java/org/myrobotlab/boofcv/ExampleVisualOdometryDepth.java deleted file mode 100644 index 45dee26c27..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/ExampleVisualOdometryDepth.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (c) 2011-2017, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Extended by Mats Önnerby to use then Kinect 360 as input instead of the .mpg files - * used in the bare bones example it's based on - * - */ - -package org.myrobotlab.boofcv; - -import java.awt.image.BufferedImage; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.ejml.data.DMatrixRMaj; -import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.BoofCv; -import org.openkinect.freenect.Resolution; -import org.slf4j.Logger; - -import boofcv.abst.feature.detect.interest.ConfigGeneralDetector; -import boofcv.abst.feature.tracker.PointTrackerTwoPass; -import boofcv.abst.sfm.AccessPointTracks3D; -import boofcv.abst.sfm.d3.DepthVisualOdometry; -import boofcv.abst.sfm.d3.VisualOdometry; -import boofcv.alg.sfm.DepthSparse3D; -import boofcv.alg.tracker.klt.PkltConfig; -import boofcv.factory.feature.tracker.FactoryPointTrackerTwoPass; -import boofcv.factory.sfm.FactoryVisualOdometry; -import boofcv.gui.image.ImagePanel; -import boofcv.gui.image.ShowImages; -import boofcv.gui.image.VisualizeImageData; -import boofcv.io.MediaManager; -import boofcv.io.calibration.CalibrationIO; -import boofcv.io.image.ConvertBufferedImage; -import boofcv.io.image.SimpleImageSequence; -import boofcv.io.wrapper.DefaultMediaManager; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.calib.VisualDepthParameters; -import boofcv.struct.distort.DoNothing2Transform2_F32; -import boofcv.struct.image.GrayS16; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.ImageType; -import georegression.struct.point.Vector3D_F64; -import georegression.struct.se.Se3_F64; - -/** - * Bare bones example showing how to estimate the camera's ego-motion using a - * depth camera system, e.g. Kinect. Additional information on the scene can be - * optionally extracted from the algorithm if it implements AccessPointTracks3D. - * - * @author Peter Abeles - */ -public class ExampleVisualOdometryDepth { - - transient public final static Logger log = LoggerFactory.getLogger(ExampleVisualOdometryDepth.class); - - Resolution resolution = Resolution.MEDIUM; - - BufferedImage buffRgb; - BufferedImage buffDepth; - - BufferedImage outRgb; - ImagePanel guiRgb; - - BufferedImage outDepth; - ImagePanel guiDepth; - - Double xTot = 0.0; - Double yTot = 0.0; - Double zTot = 0.0; - - public void process() { - - MediaManager media = DefaultMediaManager.INSTANCE; - - // String directory = UtilIO.pathExample("kinect/straight"); - String directory = Service.getResourceDir(BoofCv.class); - log.info("Using directory {}", directory); - - // load camera description and the video sequence - VisualDepthParameters param = CalibrationIO.load(media.openFile(Service.getResourceDir(BoofCv.class, "visualdepth.yaml"))); - - // specify how the image features are going to be tracked - PkltConfig configKlt = new PkltConfig(); - configKlt.pyramidScaling = new int[] { 1, 2, 4, 8 }; - configKlt.templateRadius = 3; - - PointTrackerTwoPass tracker = FactoryPointTrackerTwoPass.klt(configKlt, new ConfigGeneralDetector(600, 3, 1), GrayU8.class, GrayS16.class); - - DepthSparse3D sparseDepth = new DepthSparse3D.I<>(1e-3); - - // declares the algorithm - DepthVisualOdometry visualOdometry = FactoryVisualOdometry.depthDepthPnP(1.5, 120, 2, 200, 50, true, sparseDepth, tracker, GrayU8.class, GrayU16.class); - - // Pass in intrinsic/extrinsic calibration. This can be changed in the - // future. - visualOdometry.setCalibration(param.visualParam, new DoNothing2Transform2_F32()); - - // Process the video sequence and output the location plus number of inliers - SimpleImageSequence videoVisual = media.openVideo(directory + "/" + "rgb.mjpeg", ImageType.single(GrayU8.class)); - SimpleImageSequence videoDepth = media.openVideo(directory + "/" + "depth.mpng", ImageType.single(GrayU16.class)); - - while (videoVisual.hasNext()) { - - GrayU8 visual = videoVisual.next(); - GrayU16 depth = videoDepth.next(); - - // Handle the Depth stream - if (outDepth == null) { - BufferedImage mode = videoDepth.getGuiImage(); - depth.reshape(mode.getWidth(), mode.getHeight()); - outDepth = new BufferedImage(depth.width, depth.height, BufferedImage.TYPE_INT_RGB); - guiDepth = ShowImages.showWindow(outDepth, "Depth Image"); - } - - if (!visualOdometry.process(visual, depth)) { - throw new RuntimeException("VO Failed!"); - } - - VisualizeImageData.disparity(depth, outDepth, 0, UtilOpenKinect.FREENECT_DEPTH_MM_MAX_VALUE, 0); - guiDepth.repaint(); - - // Handle the video stream - if (outRgb == null) { - BufferedImage mode = videoDepth.getGuiImage(); - visual.reshape(mode.getWidth(), mode.getHeight()); - outRgb = new BufferedImage(visual.width, visual.height, BufferedImage.TYPE_INT_RGB); - guiRgb = ShowImages.showWindow(outRgb, "RGB Image"); - } - - ConvertBufferedImage.convertTo(visual, outRgb, true); - guiRgb.repaint(); - - Se3_F64 leftToWorld = visualOdometry.getCameraToWorld(); - Vector3D_F64 T = leftToWorld.getT(); - DMatrixRMaj R = leftToWorld.getR(); - - System.out.printf("Location %8.2f %8.2f %8.2f inliers %s\n", T.x, T.y, T.z, inlierPercent(visualOdometry)); - int cols = R.getNumCols(); - int rows = R.getNumRows(); - System.out.printf("Rotation matrix \n"); - for (int x = 0; x < cols; x++) { - for (int y = 0; y < rows; y++) { - Double rotation = R.get(x, y); - System.out.printf("%S", rotation); - } - System.out.printf("\n"); - } - System.out.printf("\n"); - xTot = xTot + T.x; - yTot = yTot + T.y; - zTot = zTot + T.z; - // System.out.printf("xT, yT, Zt, %8.2f %8.2f %8.2f \n ", xTot, yTot, - // zTot); - - } - } - - public static String toAbsolutePath(String maybeRelative) { - Path path = Paths.get(maybeRelative); - Path effectivePath = path; - if (!path.isAbsolute()) { - Path base = Paths.get(""); - effectivePath = base.resolve(path).toAbsolutePath(); - } - return effectivePath.normalize().toString(); - } - - /** - * If the algorithm implements AccessPointTracks3D, then count the number of - * inlier features and return a string. - * - * @param alg - * algorithm - * @return a string - */ - public static String inlierPercent(VisualOdometry alg) { - if (!(alg instanceof AccessPointTracks3D)) - return ""; - - AccessPointTracks3D access = (AccessPointTracks3D) alg; - - int count = 0; - int N = access.getAllTracks().size(); - for (int i = 0; i < N; i++) { - if (access.isInlier(i)) - count++; - } - - return String.format("%%%5.3f", 100.0 * count / N); - } - - public static void main(String args[]) { - ExampleVisualOdometryDepth app = new ExampleVisualOdometryDepth(); - app.process(); - } -} \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/boofcv/IntrinsicToDepthParameters.java b/src/main/java/org/myrobotlab/boofcv/IntrinsicToDepthParameters.java deleted file mode 100644 index 7fbbb0a0d8..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/IntrinsicToDepthParameters.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.io.File; - -import org.myrobotlab.framework.Service; -import org.myrobotlab.service.BoofCv; - -import boofcv.io.calibration.CalibrationIO; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.calib.CameraPinholeRadial; -import boofcv.struct.calib.VisualDepthParameters; - -/** - * Loads an intrinsic parameters file for the RGB camera and creates a - * VisualDepthParameters for the Kinect. - * - * @author Peter Abeles - */ -public class IntrinsicToDepthParameters { - - public static void main(String args[]) { - // String baseDir = UtilIO.pathExample("kinect/basket"); - String baseDir = Service.getResourceDir(BoofCv.class); - - CameraPinholeRadial intrinsic = CalibrationIO.load(new File(baseDir, "intrinsic.yaml")); - - VisualDepthParameters depth = new VisualDepthParameters(); - - depth.setVisualParam(intrinsic); - depth.setMaxDepth(UtilOpenKinect.FREENECT_DEPTH_MM_MAX_VALUE); - depth.setPixelNoDepth(UtilOpenKinect.FREENECT_DEPTH_MM_NO_VALUE); - - CalibrationIO.save(depth, baseDir + "visualdepth.yaml"); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/LogKinectDataApp.java b/src/main/java/org/myrobotlab/boofcv/LogKinectDataApp.java deleted file mode 100644 index efe6a51e35..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/LogKinectDataApp.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.image.BufferedImage; -import java.io.DataOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -import org.ddogleg.struct.GrowQueue_I8; -import org.openkinect.freenect.Context; -import org.openkinect.freenect.Device; -import org.openkinect.freenect.Freenect; -import org.openkinect.freenect.Resolution; - -import com.sun.jna.NativeLibrary; - -import boofcv.gui.image.ImagePanel; -import boofcv.gui.image.ShowImages; -import boofcv.io.image.ConvertBufferedImage; -import boofcv.io.image.UtilImageIO; -import boofcv.misc.BoofMiscOps; -import boofcv.openkinect.StreamOpenKinectRgbDepth; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; - -/** - * @author Peter Abeles - */ -public class LogKinectDataApp implements StreamOpenKinectRgbDepth.Listener { - { - // be sure to set OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY to the - // location of your shared library! - NativeLibrary.addSearchPath("freenect", OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY); - } - - int maxImages; - boolean showImage; - Resolution resolution = Resolution.MEDIUM; - - BufferedImage buffRgb; - int frameNumber; - - DataOutputStream logFile; - - GrowQueue_I8 buffer = new GrowQueue_I8(1); - - ImagePanel gui; - - public LogKinectDataApp(int maxImages, boolean showImage) { - this.maxImages = maxImages; - this.showImage = showImage; - } - - public void process() throws IOException { - - logFile = new DataOutputStream(new FileOutputStream("log/timestamps.txt")); - logFile.write("# Time stamps for rgb and depth cameras.\n".getBytes()); - - int w = UtilOpenKinect.getWidth(resolution); - int h = UtilOpenKinect.getHeight(resolution); - - buffRgb = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); - - if (showImage) { - gui = ShowImages.showWindow(buffRgb, "Kinect RGB"); - } - - StreamOpenKinectRgbDepth stream = new StreamOpenKinectRgbDepth(); - Context kinect = Freenect.createContext(); - - if (kinect.numDevices() < 0) - throw new RuntimeException("No kinect found!"); - - Device device = kinect.openDevice(0); - - stream.start(device, resolution, this); - - if (maxImages > 0) { - while (frameNumber < maxImages) { - System.out.printf("Total saved %d\n", frameNumber); - BoofMiscOps.pause(100); - } - stream.stop(); - System.out.println("Exceeded max images"); - System.exit(0); - } - } - - @Override - public void processKinect(Planar rgb, GrayU16 depth, long timeRgb, long timeDepth) { - System.out.println(frameNumber + " " + timeRgb); - try { - logFile.write(String.format("%10d %d %d\n", frameNumber, timeRgb, timeDepth).getBytes()); - logFile.flush(); - UtilImageIO.savePPM(rgb, String.format("log/rgb%07d.ppm", frameNumber), buffer); - UtilOpenKinect.saveDepth(depth, String.format("log/depth%07d.depth", frameNumber), buffer); - frameNumber++; - - if (showImage) { - ConvertBufferedImage.convertTo_U8(rgb, buffRgb, true); - gui.repaint(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static void main(String args[]) throws IOException { - LogKinectDataApp app = new LogKinectDataApp(1000000, false); - app.process(); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/OpenKinectExampleParam.java b/src/main/java/org/myrobotlab/boofcv/OpenKinectExampleParam.java deleted file mode 100644 index 448c4d63cc..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/OpenKinectExampleParam.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -/** - * Common parameter used in example code - * - * @author Peter Abeles - */ -public class OpenKinectExampleParam { - - // Modify this link to be where you store your shared library - public static String PATH_TO_SHARED_LIBRARY = "/home/pja/projects/thirdparty/libfreenect/build/lib"; -} diff --git a/src/main/java/org/myrobotlab/boofcv/OpenKinectOdometry.java b/src/main/java/org/myrobotlab/boofcv/OpenKinectOdometry.java deleted file mode 100644 index 5eff21f266..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/OpenKinectOdometry.java +++ /dev/null @@ -1,451 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.Dimension; -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -import org.ddogleg.struct.FastQueue; -import org.ejml.data.DMatrixRMaj; -import org.ejml.equation.Equation; -import org.ejml.equation.Sequence; -import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.BoofCv; -import org.openkinect.freenect.Context; -import org.openkinect.freenect.DepthFormat; -import org.openkinect.freenect.DepthHandler; -import org.openkinect.freenect.Device; -import org.openkinect.freenect.FrameMode; -import org.openkinect.freenect.Freenect; -import org.openkinect.freenect.VideoFormat; -import org.openkinect.freenect.VideoHandler; -import org.slf4j.Logger; - -import boofcv.abst.feature.detect.interest.ConfigGeneralDetector; -import boofcv.abst.feature.tracker.PointTrackerTwoPass; -import boofcv.abst.sfm.AccessPointTracks3D; -import boofcv.abst.sfm.d3.DepthVisualOdometry; -import boofcv.abst.sfm.d3.VisualOdometry; -import boofcv.alg.color.ColorRgb; -import boofcv.alg.depth.VisualDepthOps; -import boofcv.alg.geo.PerspectiveOps; -import boofcv.alg.sfm.DepthSparse3D; -import boofcv.alg.tracker.klt.PkltConfig; -import boofcv.factory.feature.tracker.FactoryPointTrackerTwoPass; -import boofcv.factory.sfm.FactoryVisualOdometry; -import boofcv.gui.image.ShowImages; -import boofcv.io.MediaManager; -import boofcv.io.calibration.CalibrationIO; -import boofcv.io.wrapper.DefaultMediaManager; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.FastQueueArray_I32; -import boofcv.struct.calib.CameraPinholeRadial; -import boofcv.struct.calib.VisualDepthParameters; -import boofcv.struct.distort.DoNothing2Transform2_F32; -import boofcv.struct.image.GrayS16; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; -import boofcv.visualize.PointCloudViewer; -import boofcv.visualize.VisualizeData; -import georegression.struct.point.Point3D_F64; -import georegression.struct.point.Vector3D_F64; -import georegression.struct.se.Se3_F64; - -/** - * Example demonstrating how to process and display data from the Kinect. - * - * @author Peter Abeles and Mats Önnerby - * - * This program currently calculates the Odometry ( how much the camera - * has moved as translations and rotations ). Second stage is to - * recalculate the second pointcloud to have the same origin as the - * first. Third stage is to update the original pointcloud with - * information from the new pointcloud to make the world pointcloud - * larger Do we need to keep track of the probability for each point in - * the pointcloud ? Can we transform the pointcloud to larger objects ? - * Like meshes ? - * https://www.mathworks.com/help/vision/examples/3-d-point-cloud-registration-and-stitching.html - * - */ -public class OpenKinectOdometry { - - static final Logger log = LoggerFactory.getLogger(OpenKinectOdometry.class); - - private PointCloudViewer viewer; - private PointCloudViewer viewerFixed; - - private volatile boolean videoAvailable = false; - private volatile boolean depthAvailable = false; - - boolean firstDepth = true; - boolean firstVideo = true; - boolean firstImage = true; - - String baseDir = Service.getResourceDir(BoofCv.class); - String nameCalib = "intrinsic.yaml"; - - Planar rgb = new Planar<>(GrayU8.class, 1, 1, 3); - GrayU16 depth = new GrayU16(1, 1); - - List points = new ArrayList(); - List pointsFixed = new ArrayList(); - int colors[] = new int[1]; - - boolean odometry = true; - - DepthVisualOdometry visualOdometry; - - Double xTot = 0.0; - Double yTot = 0.0; - Double zTot = 0.0; - - // Matrix multiplication initiation - Sequence transform, invert; - DMatrixRMaj pointMatIn = new DMatrixRMaj(4, 1); - DMatrixRMaj pointMatOut = new DMatrixRMaj(4, 1); - DMatrixRMaj transMat = new DMatrixRMaj(4, 4); - Equation eq = new Equation(); - - public void process() { - - Context kinect = Freenect.createContext(); - - if (kinect.numDevices() < 0) - throw new RuntimeException("No kinect found!"); - - Device device = kinect.openDevice(0); - - device.setDepthFormat(DepthFormat.REGISTERED); - device.setVideoFormat(VideoFormat.RGB); - - CameraPinholeRadial param = CalibrationIO.load(new File(baseDir, nameCalib)); - - FastQueue cloud = new FastQueue<>(Point3D_F64.class, true); - FastQueueArray_I32 cloudColor = new FastQueueArray_I32(3); - - if (odometry) { - initOdometry(param); - } - - viewer = VisualizeData.createPointCloudViewer(); - viewer.setCameraHFov(PerspectiveOps.computeHFov(param)); - viewer.setTranslationStep(15); - - device.startDepth(new DepthHandler() { - @Override - public void onFrameReceived(FrameMode mode, ByteBuffer frame, int timestamp) { - processDepth(mode, frame, timestamp); - } - }); - device.startVideo(new VideoHandler() { - @Override - public void onFrameReceived(FrameMode mode, ByteBuffer frame, int timestamp) { - processRgb(mode, frame, timestamp); - } - }); - - long starTime = System.currentTimeMillis(); - while (starTime + 200000 > System.currentTimeMillis()) { - - if (videoAvailable && depthAvailable) { - if (firstImage) { - viewer.getComponent().setPreferredSize(new Dimension(rgb.width, rgb.height)); - if (odometry) { - viewerFixed.getComponent().setPreferredSize(new Dimension(rgb.width, rgb.height)); - } - } - - VisualDepthOps.depthTo3D(param, rgb, depth, cloud, cloudColor); - - if (colors.length != cloud.size()) { - colors = new int[cloud.size()]; - points = new ArrayList(cloud.size()); - } - - points.clear(); - for (int i = 0; i < cloud.size; i++) { - Point3D_F64 p = cloud.get(i); - int[] color = cloudColor.get(i); - int c = (color[0] << 16) | (color[1] << 8) | color[2]; - points.add(p); - colors[i] = c; - } - - log.info("Points size() 1 ", points.size(), "\n"); - viewer.clearPoints(); - viewer.addCloud(points, colors); - - if (firstImage) { - ShowImages.showWindow(viewer.getComponent(), "Point Cloud", true); - if (odometry) { - ShowImages.showWindow(viewerFixed.getComponent(), "Point Cloud Fixed", true); - } - firstImage = false; - } - - // Odometry - if (odometry) { - processOdometry(); - } - - viewer.getComponent().repaint(); - videoAvailable = false; - depthAvailable = false; - } - sleep(10); - } - System.out.println("100 Seconds elapsed"); - - device.stopDepth(); - device.stopVideo(); - device.close(); - } - - private void initOdometry(CameraPinholeRadial param) { - // Initiate and precompile Matrix operations - eq.alias(pointMatIn, "in", transMat, "R", pointMatOut, "out"); - eq.alias(new DMatrixRMaj(1, 1), "in"); - eq.alias(new DMatrixRMaj(1, 1), "R"); - eq.alias(new DMatrixRMaj(1, 1), "out"); - transform = eq.compile("out = R*in"); - invert = eq.compile("R = inv(R)"); - eq.alias(pointMatIn, "in", transMat, "R", pointMatOut, "out"); - - MediaManager media = DefaultMediaManager.INSTANCE; - // String directory = UtilIO.pathExample("kinect/straight"); - String directory = Service.getResourceDir(BoofCv.class) + File.separator; - log.info("Using directory ", directory); - - // load camera description and the video sequence - VisualDepthParameters depthParam = CalibrationIO.load(media.openFile(directory + "visualdepth.yaml")); - - // specify how the image features are going to be tracked - PkltConfig configKlt = new PkltConfig(); - configKlt.pyramidScaling = new int[] { 1, 2, 4, 8 }; - configKlt.templateRadius = 3; - - PointTrackerTwoPass tracker = FactoryPointTrackerTwoPass.klt(configKlt, new ConfigGeneralDetector(600, 3, 1), GrayU8.class, GrayS16.class); - - DepthSparse3D sparseDepth = new DepthSparse3D.I<>(1e-3); - - // declares the algorithm - visualOdometry = FactoryVisualOdometry.depthDepthPnP(1.5, 120, 2, 200, 50, true, sparseDepth, tracker, GrayU8.class, GrayU16.class); - - // Pass in intrinsic/extrinsic calibration. This can be changed in the - // future. - visualOdometry.setCalibration(depthParam.visualParam, new DoNothing2Transform2_F32()); - - viewerFixed = VisualizeData.createPointCloudViewer(); - viewerFixed.setCameraHFov(PerspectiveOps.computeHFov(param)); - viewerFixed.setTranslationStep(15); - } - - private void processOdometry() { - - GrayU8 gray = new GrayU8(rgb.width, rgb.height); - ColorRgb.rgbToGray_Weighted(rgb, gray); - if (!visualOdometry.process(gray, depth)) { - throw new RuntimeException("VO Failed!"); - } - - Se3_F64 leftToWorld = visualOdometry.getCameraToWorld(); - Vector3D_F64 T = leftToWorld.getT(); - DMatrixRMaj R = leftToWorld.getR(); - System.out.printf("Location %8.2f %8.2f %8.2f inliers %s\n", T.x, T.y, T.z, inlierPercent(visualOdometry)); - - int cols = R.getNumCols(); - int rows = R.getNumRows(); - System.out.printf("Rotation matrix \n"); - for (int x = 0; x < cols; x++) { - for (int y = 0; y < rows; y++) { - Double rotation = R.get(x, y); - System.out.printf("%8.2f", rotation); - } - System.out.printf("\n"); - } - System.out.printf("\n"); - pointsFixed.clear(); - // Rotate and transform - - // Load the (4*4) Transformation matrix from Rotations (3*3) and - // Translations (3*1) - // From the Odometry - transMat.set(0, 0, R.get(0, 0)); - transMat.set(1, 0, R.get(1, 0)); - transMat.set(2, 0, R.get(2, 0)); - transMat.set(3, 0, T.getX()); - transMat.set(0, 1, R.get(0, 0)); - transMat.set(1, 1, R.get(1, 1)); - transMat.set(2, 1, R.get(2, 1)); - transMat.set(3, 1, T.getY()); - transMat.set(0, 2, R.get(0, 2)); - transMat.set(1, 2, R.get(1, 2)); - transMat.set(2, 2, R.get(2, 2)); - transMat.set(3, 2, T.getZ()); - transMat.set(0, 3, 0.0); - transMat.set(1, 3, 0.0); - transMat.set(2, 3, 0.0); - transMat.set(3, 3, 1.0); - - // Load the (4*4) Transformation matrix from Rotations (3*3) and - // Translations (3*1) - // No rotation or transformation (unit matrix) - // 1 0 0 0 - // 0 1 0 0 - // 0 0 1 0 - // 0 0 0 1 - transMat.set(0, 0, 1.0); - transMat.set(1, 0, 0.0); - transMat.set(2, 0, 0.0); - transMat.set(3, 0, 0.0); // Translate X - transMat.set(0, 1, 0.0); - transMat.set(1, 1, 1.0); - transMat.set(2, 1, 0.0); - transMat.set(3, 1, 0.0); // Translate Y - transMat.set(0, 2, 0.0); - transMat.set(1, 2, 0.0); - transMat.set(2, 2, 1.0); - transMat.set(3, 2, 0.0); // Translate Z - transMat.set(0, 3, 0.0); - transMat.set(1, 3, 0.0); - transMat.set(2, 3, 0.0); - transMat.set(3, 3, 1.0); - - log.info("Points size() 2", points.size(), "\n"); - for (int i = 0; i < points.size(); i++) { - // Transform from cartesian to homogenous coordinates ( 4 dimensions ) - Point3D_F64 p = points.get(i); - pointMatIn.set(0, 0, p.getX()); - pointMatIn.set(1, 0, p.getY()); - pointMatIn.set(2, 0, p.getZ()); - pointMatIn.set(3, 0, 1.0); - // This statement executes the Matrix multiplication defined in transform - // Log.info("Points in ", pointMatIn.get(0, 0), " ", pointMatIn.get(1, 0), - // " ", pointMatIn.get(2, 0), " ", pointMatIn.get(3, - // 0)); - transform.perform(); - // Log.info("Points out ", pointMatOut.get(0, 0), " ", pointMatOut.get(1, - // 0), " ", pointMatOut.get(2, 0), " ", - // pointMatOut.get(3, 0)); - Point3D_F64 pFixed = new Point3D_F64(pointMatOut.get(0, 0) / pointMatOut.get(3, 0), pointMatOut.get(1, 0) / pointMatOut.get(3, 0), - pointMatOut.get(2, 0) / pointMatOut.get(3, 0)); - pointsFixed.add(pFixed); - } - - viewerFixed.clearPoints(); - viewerFixed.addCloud(pointsFixed, colors); - viewerFixed.getComponent().repaint(); - - } - - private void sleep(int millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException e) { - } - - } - - protected void processDepth(FrameMode mode, ByteBuffer frame, int timestamp) { - - // System.out.println("Got depth! "+timestamp); - if (firstDepth) { - depth.reshape(mode.getWidth(), mode.getHeight()); - firstDepth = false; - } - - // Skip frames until the previous depth map has been shown - if (!depthAvailable) { - // Convert the frame to a depth map)) - UtilOpenKinect.bufferDepthToU16(frame, depth); - // Log.info("frame.capacity", frame.capacity()); - // Log.info("depthAvailable", depth.width, depth.height); - depthAvailable = true; - } - } - - protected void processRgb(FrameMode mode, ByteBuffer frame, int timestamp) { - - if (mode.getVideoFormat() != VideoFormat.RGB) { - System.out.println("Bad rgb format!"); - } - - // System.out.println("Got rgb! "+timestamp); - if (firstVideo) { - rgb.reshape(mode.getWidth(), mode.getHeight()); - viewer.getComponent().setPreferredSize(new Dimension(rgb.width, rgb.height)); - firstVideo = false; - } - - // Skip frames until the previous video has been shown - if (!videoAvailable) { - UtilOpenKinect.bufferRgbToMsU8(frame, rgb); - // Log.info("videoAvailable"); - videoAvailable = true; - } - - } - - /** - * If the algorithm implements AccessPointTracks3D, then count the number of - * inlier features and return a string. - * - * @param alg - * algorithm - * @return a string - */ - public static String inlierPercent(VisualOdometry alg) { - if (!(alg instanceof AccessPointTracks3D)) - return ""; - - AccessPointTracks3D access = (AccessPointTracks3D) alg; - - int count = 0; - int N = access.getAllTracks().size(); - for (int i = 0; i < N; i++) { - if (access.isInlier(i)) - count++; - } - - return String.format("%%%5.3f", 100.0 * count / N); - } - - public static String toAbsolutePath(String maybeRelative) { - Path path = Paths.get(maybeRelative); - Path effectivePath = path; - if (!path.isAbsolute()) { - Path base = Paths.get(""); - effectivePath = base.resolve(path).toAbsolutePath(); - } - return effectivePath.normalize().toString(); - } - - public static void main(String args[]) { - OpenKinectOdometry app = new OpenKinectOdometry(); - - app.process(); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/OpenKinectPointCloud.java b/src/main/java/org/myrobotlab/boofcv/OpenKinectPointCloud.java deleted file mode 100644 index 924b57935a..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/OpenKinectPointCloud.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.Dimension; -import java.io.File; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import org.ddogleg.struct.FastQueue; -import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.BoofCv; -import org.openkinect.freenect.Context; -import org.openkinect.freenect.DepthFormat; -import org.openkinect.freenect.DepthHandler; -import org.openkinect.freenect.Device; -import org.openkinect.freenect.FrameMode; -import org.openkinect.freenect.Freenect; -import org.openkinect.freenect.VideoFormat; -import org.openkinect.freenect.VideoHandler; -import org.slf4j.Logger; - -import com.sun.jna.NativeLibrary; - -import boofcv.alg.depth.VisualDepthOps; -import boofcv.alg.geo.PerspectiveOps; -import boofcv.gui.image.ShowImages; -import boofcv.io.calibration.CalibrationIO; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.FastQueueArray_I32; -import boofcv.struct.calib.CameraPinholeRadial; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; -import boofcv.visualize.PointCloudViewer; -import boofcv.visualize.VisualizeData; -import georegression.struct.point.Point3D_F64; - -/** - * Example demonstrating how to process and display data from the Kinect. - * - * @author Peter Abeles and Mats Önnerby - */ -public class OpenKinectPointCloud { - - { - // be sure to set OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY to the - // location of your shared library! - NativeLibrary.addSearchPath("freenect", OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY); - } - - static final Logger log = LoggerFactory.getLogger(OpenKinectPointCloud.class); - - private PointCloudViewer viewer; - - private volatile boolean videoAvailable = false; - private volatile boolean depthAvailable = false; - - boolean firstDepth = true; - boolean firstVideo = true; - boolean firstImage = true; - - String baseDir = Service.getResourceDir(BoofCv.class); - String nameCalib = "intrinsic.yaml"; - - Planar rgb = new Planar<>(GrayU8.class, 1, 1, 3); - GrayU16 depth = new GrayU16(1, 1); - - List points = new ArrayList(); - int colors[] = new int[1]; - - public void process() { - - Context kinect = Freenect.createContext(); - - if (kinect.numDevices() < 0) - throw new RuntimeException("No kinect found!"); - - Device device = kinect.openDevice(0); - - device.setDepthFormat(DepthFormat.REGISTERED); - device.setVideoFormat(VideoFormat.RGB); - - CameraPinholeRadial param = CalibrationIO.load(new File(baseDir, nameCalib)); - - FastQueue cloud = new FastQueue<>(Point3D_F64.class, true); - FastQueueArray_I32 cloudColor = new FastQueueArray_I32(3); - - viewer = VisualizeData.createPointCloudViewer(); - viewer.setCameraHFov(PerspectiveOps.computeHFov(param)); - viewer.setTranslationStep(15); - - device.startDepth(new DepthHandler() { - @Override - public void onFrameReceived(FrameMode mode, ByteBuffer frame, int timestamp) { - processDepth(mode, frame, timestamp); - } - }); - device.startVideo(new VideoHandler() { - @Override - public void onFrameReceived(FrameMode mode, ByteBuffer frame, int timestamp) { - processRgb(mode, frame, timestamp); - } - }); - - long starTime = System.currentTimeMillis(); - while (starTime + 100000 > System.currentTimeMillis()) { - - if (videoAvailable && depthAvailable) { - if (firstImage) { - viewer.getComponent().setPreferredSize(new Dimension(rgb.width, rgb.height)); - } - - VisualDepthOps.depthTo3D(param, rgb, depth, cloud, cloudColor); - - if (colors.length != cloud.size()) { - colors = new int[cloud.size()]; - points = new ArrayList(cloud.size()); - } - - points.clear(); - for (int i = 0; i < cloud.size; i++) { - Point3D_F64 p = cloud.get(i); - int[] color = cloudColor.get(i); - int c = (color[0] << 16) | (color[1] << 8) | color[2]; - points.add(p); - colors[i] = c; - } - - viewer.clearPoints(); - if (colors.length != points.size()) { - log.info("WTF", colors.length, points.size(), cloud.size); - } - viewer.addCloud(points, colors); - - if (firstImage) { - ShowImages.showWindow(viewer.getComponent(), "Point Cloud", true); - firstImage = false; - } else { - viewer.getComponent().repaint(); - } - videoAvailable = false; - depthAvailable = false; - } - sleep(10); - } - System.out.println("100 Seconds elapsed"); - - device.stopDepth(); - device.stopVideo(); - device.close(); - } - - private void sleep(int millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException e) { - } - - } - - protected void processDepth(FrameMode mode, ByteBuffer frame, int timestamp) { - - // System.out.println("Got depth! "+timestamp); - if (firstDepth) { - depth.reshape(mode.getWidth(), mode.getHeight()); - firstDepth = false; - } - - // Skip frames until the previous depth map has been shown - if (!depthAvailable) { - // Convert the frame to a depth map)) - UtilOpenKinect.bufferDepthToU16(frame, depth); - // Log.info("frame.capacity", frame.capacity()); - // Log.info("depthAvailable", depth.width, depth.height); - depthAvailable = true; - } - } - - protected void processRgb(FrameMode mode, ByteBuffer frame, int timestamp) { - - if (mode.getVideoFormat() != VideoFormat.RGB) { - System.out.println("Bad rgb format!"); - } - - // System.out.println("Got rgb! "+timestamp); - if (firstVideo) { - rgb.reshape(mode.getWidth(), mode.getHeight()); - viewer.getComponent().setPreferredSize(new Dimension(rgb.width, rgb.height)); - firstVideo = false; - } - - // Skip frames until the previous video has been shown - if (!videoAvailable) { - UtilOpenKinect.bufferRgbToMsU8(frame, rgb); - // Log.info("videoAvailable"); - videoAvailable = true; - } - - } - - public static void main(String args[]) { - OpenKinectPointCloud app = new OpenKinectPointCloud(); - - app.process(); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/OpenKinectStreamingTest.java b/src/main/java/org/myrobotlab/boofcv/OpenKinectStreamingTest.java deleted file mode 100644 index 4cb9e28641..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/OpenKinectStreamingTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.image.BufferedImage; -import java.nio.ByteBuffer; - -import org.openkinect.freenect.Context; -import org.openkinect.freenect.DepthFormat; -import org.openkinect.freenect.DepthHandler; -import org.openkinect.freenect.Device; -import org.openkinect.freenect.FrameMode; -import org.openkinect.freenect.Freenect; -import org.openkinect.freenect.VideoFormat; -import org.openkinect.freenect.VideoHandler; - -import com.sun.jna.NativeLibrary; - -import boofcv.gui.image.ImagePanel; -import boofcv.gui.image.ShowImages; -import boofcv.gui.image.VisualizeImageData; -import boofcv.io.image.ConvertBufferedImage; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; - -/** - * Example demonstrating how to process and display data from the Kinect. - * - * @author Peter Abeles - */ -public class OpenKinectStreamingTest { - - { - // be sure to set OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY to the - // location of your shared library! - NativeLibrary.addSearchPath("freenect", OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY); - } - - Planar rgb = new Planar<>(GrayU8.class, 1, 1, 3); - GrayU16 depth = new GrayU16(1, 1); - - BufferedImage outRgb; - ImagePanel guiRgb; - - BufferedImage outDepth; - ImagePanel guiDepth; - - public void process() { - Context kinect = Freenect.createContext(); - - if (kinect.numDevices() < 0) - throw new RuntimeException("No kinect found!"); - - Device device = kinect.openDevice(0); - - device.setDepthFormat(DepthFormat.REGISTERED); - device.setVideoFormat(VideoFormat.RGB); - - device.startDepth(new DepthHandler() { - @Override - public void onFrameReceived(FrameMode mode, ByteBuffer frame, int timestamp) { - processDepth(mode, frame, timestamp); - } - }); - device.startVideo(new VideoHandler() { - @Override - public void onFrameReceived(FrameMode mode, ByteBuffer frame, int timestamp) { - processRgb(mode, frame, timestamp); - } - }); - - long starTime = System.currentTimeMillis(); - while (starTime + 100000 > System.currentTimeMillis()) { - } - System.out.println("100 Seconds elapsed"); - - device.stopDepth(); - device.stopVideo(); - device.close(); - - } - - protected void processDepth(FrameMode mode, ByteBuffer frame, int timestamp) { - System.out.println("Got depth! " + timestamp); - - if (outDepth == null) { - depth.reshape(mode.getWidth(), mode.getHeight()); - outDepth = new BufferedImage(depth.width, depth.height, BufferedImage.TYPE_INT_RGB); - guiDepth = ShowImages.showWindow(outDepth, "Depth Image"); - } - - UtilOpenKinect.bufferDepthToU16(frame, depth); - - // VisualizeImageData.grayUnsigned(depth,outDepth,UtilOpenKinect.FREENECT_DEPTH_MM_MAX_VALUE); - VisualizeImageData.disparity(depth, outDepth, 0, UtilOpenKinect.FREENECT_DEPTH_MM_MAX_VALUE, 0); - guiDepth.repaint(); - } - - protected void processRgb(FrameMode mode, ByteBuffer frame, int timestamp) { - if (mode.getVideoFormat() != VideoFormat.RGB) { - System.out.println("Bad rgb format!"); - } - - System.out.println("Got rgb! " + timestamp); - - if (outRgb == null) { - rgb.reshape(mode.getWidth(), mode.getHeight()); - outRgb = new BufferedImage(rgb.width, rgb.height, BufferedImage.TYPE_INT_RGB); - guiRgb = ShowImages.showWindow(outRgb, "RGB Image"); - } - - UtilOpenKinect.bufferRgbToMsU8(frame, rgb); - ConvertBufferedImage.convertTo_U8(rgb, outRgb, true); - - guiRgb.repaint(); - } - - public static void main(String args[]) { - OpenKinectStreamingTest app = new OpenKinectStreamingTest(); - - app.process(); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/OverlayRgbDepthStreamsApp.java b/src/main/java/org/myrobotlab/boofcv/OverlayRgbDepthStreamsApp.java deleted file mode 100644 index b35c089f78..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/OverlayRgbDepthStreamsApp.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.AlphaComposite; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; - -import org.openkinect.freenect.Context; -import org.openkinect.freenect.Device; -import org.openkinect.freenect.Freenect; -import org.openkinect.freenect.Resolution; - -import com.sun.jna.NativeLibrary; - -import boofcv.gui.image.ImagePanel; -import boofcv.gui.image.ShowImages; -import boofcv.gui.image.VisualizeImageData; -import boofcv.io.image.ConvertBufferedImage; -import boofcv.openkinect.StreamOpenKinectRgbDepth; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; - -/** - * @author Peter Abeles - */ -public class OverlayRgbDepthStreamsApp implements StreamOpenKinectRgbDepth.Listener { - { - // be sure to set OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY to the - // location of your shared library! - NativeLibrary.addSearchPath("freenect", OpenKinectExampleParam.PATH_TO_SHARED_LIBRARY); - } - - Resolution resolution = Resolution.MEDIUM; - - BufferedImage buffRgb; - BufferedImage buffDepth; - - ImagePanel gui; - - public void process() { - - int w = UtilOpenKinect.getWidth(resolution); - int h = UtilOpenKinect.getHeight(resolution); - - buffRgb = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); - buffDepth = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); - - gui = ShowImages.showWindow(buffRgb, "Kinect Overlay"); - - StreamOpenKinectRgbDepth stream = new StreamOpenKinectRgbDepth(); - Context kinect = Freenect.createContext(); - - if (kinect.numDevices() < 0) - throw new RuntimeException("No kinect found!"); - - Device device = kinect.openDevice(0); - stream.start(device, resolution, this); - } - - @Override - public void processKinect(Planar rgb, GrayU16 depth, long timeRgb, long timeDepth) { - VisualizeImageData.disparity(depth, buffDepth, 0, UtilOpenKinect.FREENECT_DEPTH_MM_MAX_VALUE, 0); - ConvertBufferedImage.convertTo_U8(rgb, buffRgb, true); - - Graphics2D g2 = buffRgb.createGraphics(); - float alpha = 0.5f; - int type = AlphaComposite.SRC_OVER; - AlphaComposite composite = AlphaComposite.getInstance(type, alpha); - g2.setComposite(composite); - g2.drawImage(buffDepth, 0, 0, null); - - gui.repaint(); - } - - public static void main(String args[]) { - OverlayRgbDepthStreamsApp app = new OverlayRgbDepthStreamsApp(); - app.process(); - } -} diff --git a/src/main/java/org/myrobotlab/boofcv/PlaybackKinectLogApp.java b/src/main/java/org/myrobotlab/boofcv/PlaybackKinectLogApp.java deleted file mode 100644 index c4a3603a21..0000000000 --- a/src/main/java/org/myrobotlab/boofcv/PlaybackKinectLogApp.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved. - * - * This file is part of BoofCV (http://boofcv.org). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.myrobotlab.boofcv; - -import java.awt.image.BufferedImage; -import java.io.IOException; - -import org.ddogleg.struct.GrowQueue_I8; - -import boofcv.gui.image.ImageGridPanel; -import boofcv.gui.image.ShowImages; -import boofcv.gui.image.VisualizeImageData; -import boofcv.io.image.ConvertBufferedImage; -import boofcv.io.image.UtilImageIO; -import boofcv.misc.BoofMiscOps; -import boofcv.openkinect.UtilOpenKinect; -import boofcv.struct.image.GrayU16; -import boofcv.struct.image.GrayU8; -import boofcv.struct.image.Planar; - -/** - * @author Peter Abeles - */ -public class PlaybackKinectLogApp { - ImageGridPanel gui; - - String directory; - - GrowQueue_I8 data = new GrowQueue_I8(); - boolean depthIsPng = false; - - // image with depth information - private GrayU16 depth = new GrayU16(1, 1); - // image with color information - private Planar rgb = new Planar<>(GrayU8.class, 1, 1, 3); - - BufferedImage outRgb; - BufferedImage outDepth; - - public PlaybackKinectLogApp(String directory) { - this.directory = directory; - } - - public void process() throws IOException { - parseFrame(0); - - outRgb = new BufferedImage(rgb.getWidth(), rgb.getHeight(), BufferedImage.TYPE_INT_RGB); - outDepth = new BufferedImage(depth.getWidth(), depth.getHeight(), BufferedImage.TYPE_INT_RGB); - - gui = new ImageGridPanel(1, 2, outRgb, outDepth); - ShowImages.showWindow(gui, "Kinect Data"); - - int frame = 1; - while (true) { - parseFrame(frame++); - ConvertBufferedImage.convertTo_U8(rgb, outRgb, true); - VisualizeImageData.disparity(depth, outDepth, 0, UtilOpenKinect.FREENECT_DEPTH_MM_MAX_VALUE, 0); - gui.repaint(); - BoofMiscOps.pause(30); - } - } - - private void parseFrame(int frameNumber) throws IOException { - UtilImageIO.loadPPM_U8(String.format("%s/rgb%07d.ppm", directory, frameNumber), rgb, data); - if (depthIsPng) { - BufferedImage image = UtilImageIO.loadImage(String.format("%s/depth%07d.png", directory, frameNumber)); - ConvertBufferedImage.convertFrom(image, depth, true); - } else { - UtilOpenKinect.parseDepth(String.format("%s/depth%07d.depth", directory, frameNumber), depth, data); - } - - } - - public static void main(String args[]) throws IOException { - PlaybackKinectLogApp app = new PlaybackKinectLogApp("log"); - - app.process(); - } -} diff --git a/src/main/java/org/myrobotlab/service/interfaces/ComputerVision.java b/src/main/java/org/myrobotlab/cv/ComputerVision.java similarity index 81% rename from src/main/java/org/myrobotlab/service/interfaces/ComputerVision.java rename to src/main/java/org/myrobotlab/cv/ComputerVision.java index cee01671db..56ba55e2b5 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/ComputerVision.java +++ b/src/main/java/org/myrobotlab/cv/ComputerVision.java @@ -1,6 +1,5 @@ -package org.myrobotlab.service.interfaces; +package org.myrobotlab.cv; -import org.myrobotlab.cv.CvFilter; import org.myrobotlab.framework.interfaces.NameProvider; /** diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilterKinectPointCloud.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilterKinectPointCloud.java deleted file mode 100644 index c3906f34da..0000000000 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilterKinectPointCloud.java +++ /dev/null @@ -1,310 +0,0 @@ -/** - * - * @author grog (at) myrobotlab.org - * - * This file is part of MyRobotLab (http://myrobotlab.org). - * - * MyRobotLab is free software: you can redistribute it and/or modify - * it under the terms of the Apache License 2.0 as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version (subject to the "Classpath" exception - * as provided in the LICENSE.txt file that accompanied this code). - * - * MyRobotLab is distributed in the hope that it will be useful or fun, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Apache License 2.0 for more details. - * - * All libraries in thirdParty bundle are subject to their own license - * requirements - please refer to http://myrobotlab.org/libraries for - * details. - * - * Enjoy ! - * - * */ - -package org.myrobotlab.opencv; - -import static org.bytedeco.opencv.global.opencv_core.IPL_DEPTH_8U; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.math3.geometry.euclidean.threed.SphericalCoordinates; -import org.bytedeco.javacpp.indexer.UByteIndexer; -import org.bytedeco.javacpp.indexer.UShortRawIndexer; -import org.bytedeco.javacv.Parallel; -import org.bytedeco.opencv.opencv_core.AbstractIplImage; -import org.bytedeco.opencv.opencv_core.IplImage; -import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.logging.LoggingFactory; -import org.myrobotlab.math.geometry.Point; -import org.myrobotlab.math.geometry.Point3df; -import org.myrobotlab.math.geometry.PointCloud; -import org.myrobotlab.service.BoofCv; -import org.myrobotlab.service.JMonkeyEngine; -import org.myrobotlab.service.OpenCV; -import org.myrobotlab.service.Runtime; -import org.slf4j.Logger; - -import boofcv.alg.distort.radtan.RemoveRadialPtoN_F64; -import boofcv.io.calibration.CalibrationIO; -import boofcv.struct.calib.CameraPinholeRadial; -import georegression.struct.point.Point2D_F64; - -/** - *
    - * 
    - * @author GroG
    - * 
    - * references :
    - *  http://blog.elonliu.com/2017/03/18/kinect-coordinate-mapping-summary-and-pitfalls/
    - *  https://smeenk.com/kinect-field-of-view-comparison/
    - *  https://stackoverflow.com/questions/17832238/kinect-intrinsic-parameters-from-field-of-view/18199938#18199938
    - *  https://answers.ros.org/question/195034/coordinates-of-a-specific-pixel-in-depthimage-published-by-kinect/
    - * 
    - * 
    - */ -public class OpenCVFilterKinectPointCloud extends OpenCVFilter { - - // useful data for the kinect is 632 X 480 - 8 pixels on the right edge are - // not good data - // http://groups.google.com/group/openkinect/browse_thread/thread/6539281cf451ae9e?pli=1 - - public final static Logger log = LoggerFactory.getLogger(OpenCVFilterKinectPointCloud.class); - - private static final long serialVersionUID = 1L; - - public boolean clearPoints = false; - - transient IplImage lastDepth = null; - - IplImage returnImage = null; - - /** - * list of samplepoint to return depth - */ - List samplePoints = new ArrayList<>(); - - PointCloud pointCloud = null; - - int colors[] = null; - - SphericalCoordinates transformer = null; - - boolean clearSamplePoints = false; - - Point3df cameraLocation = new Point3df(); - // pitch yaw heading necessary ? - or heading suffice ? - float cameraHeading = 0; - float cameraTilt = 0;// degrees ? - double r, theta, phi; - Point3df[] depthBuffer; - float[] colorBuffer; - IplImage color; - // double focalLength = h / 2 * Math.tan((43 * 0.0174533)/2); - // BoofCv - RemoveRadialPtoN_F64 p2n = null; - - int width, height = 0; - - public OpenCVFilterKinectPointCloud(String name) { - super(name); - - String baseDir = Service.getResourceDir(BoofCv.class); - String nameCalib = "intrinsic.yaml"; - - CameraPinholeRadial param = CalibrationIO.load(new File(baseDir, nameCalib)); - p2n = new RemoveRadialPtoN_F64(); - p2n.setK(param.fx, param.fy, param.skew, param.cx, param.cy).setDistortion(param.radial, param.t1, param.t2); - - // http://myrobotlab.org/content/useful-kinect-info - // vertical focal length - theta = 43 * 0.017453; - phi = 57 * 0.017453; - r = 1; - } - - @Override - public void imageChanged(IplImage image) { - width = image.width(); - height = image.height(); - } - - @Override - public IplImage process(IplImage depth) throws InterruptedException { - - if (depth.depth() != 16 && depth.nChannels() != 1) { - log.error("not valid kinect depth image expecting 1 channel 16 depth got {} channel {} depth", depth.depth(), depth.nChannels()); - return depth; - } - - if (transformer == null) { - transformer = new SphericalCoordinates(r, theta, phi); - } - - lastDepth = depth; - - if (clearSamplePoints) { - samplePoints.clear(); - clearSamplePoints = false; - } - - if (color == null) { - color = AbstractIplImage.create(depth.width(), depth.height(), IPL_DEPTH_8U, 3); - } - - final UShortRawIndexer depthIdx = (UShortRawIndexer) depth.createIndexer(); - final UByteIndexer colorIdx = color.createIndexer(); - - // f = camera focal length - // xv = x viewport - // yv = y viewport - - // w = screen width - // h = screen height - - // xw = x world coordinate - // xy = y world coordinate - // zy = z world coordinate - - // buffer = FloatBuffer.allocate(depth.width() * depth.height()); - depthBuffer = new Point3df[width * height]; - if (colorBuffer == null) { - colorBuffer = new float[width * height * 4]; // RGBA - } - - /** - *
    -     * for (int i = 0; i < color.width() * color.height(); ++i) {
    -     *   colorBuffer[i] = 0.5f;
    -     *   colorBuffer[i + 1] = 0.5f;
    -     *   colorBuffer[i + 2] = i % 100 * .999f;
    -     *   colorBuffer[i + 3] = 1f;
    -     * }
    -     * 
    - */ - - Point2D_F64 n = new Point2D_F64(); - - Parallel.loop(0, height, new Parallel.Looper() { - @Override - public void loop(int from, int to, int looperID) { - for (int xv = from; xv < to; xv++) { - for (int yv = 0; yv < width; yv++) { - - // FIXME - find correct scale for x & y in mm - - double xw, yw, zw = 0; - float depth = depthIdx.get(xv, yv); - - // zw = D * f / sqrt(xv² + yv² + f²) - // https://hub.jmonkeyengine.org/t/point-cloud-visualization/25838 - - int index = yv * height + xv; - // colorIdx.put(index, 33); - // colorIdx.put(index+1, 33); - - /** - *
    -             * BoofCv way p2n.compute(xv,yv,n); Point3D_F64 p = new
    -             * Point3D_F64(); p.z = depth; p.x = n.x*p.z; p.y = n.y*p.z;
    -             * 
    -             * float scaled = depth/1000;
    -             * 
    -             * yw = n.y*scaled; xw = n.x*scaled; zw = scaled; // really ?
    -             */
    -
    -            zw = -1 * depth / 1000; // we want in 1 meter world unit
    -            xw = 2 * (xv - 639 / 2) * Math.tan(57 / 2 * 0.0174533) * (zw / 640);
    -            yw = 2 * (479 - yv - 479 / 2) * Math.tan(43 / 2 * 0.0174533) * (zw / 480);
    -
    -            // points[index] = new Point3df((float)xw,(float)yw,(float)zw);
    -            // jmonkey has a flipped y/x
    -            depthBuffer[index] = new Point3df((float) yw, (float) xw, (float) zw);
    -            if (color != null) {
    -
    -            }
    -          }
    -        }
    -      }
    -    });
    -
    -    pointCloud = new PointCloud(depthBuffer);
    -    pointCloud.setColors(colorBuffer);
    -
    -    // NO MORE PUBLISHING - just put into OpenCVData !!!
    -    // publishPointCloud(pointCloud);
    -
    -    put(pointCloud);
    -
    -    depthIdx.release();
    -    colorIdx.release();
    -
    -    return depth;
    -  }
    -
    -  public void publishPlane() {
    -    // spin through sample points
    -    for (int i = 0; i < samplePoints.size(); ++i) {
    -
    -    }
    -  }
    -
    -  @Override
    -  public BufferedImage processDisplay(Graphics2D graphics, BufferedImage image) {
    -    if (lastDepth == null) {
    -      return image;
    -    }
    -    ByteBuffer buffer = lastDepth.getByteBuffer();
    -    for (Point point : samplePoints) {
    -
    -      int depthBytesPerChannel = lastDepth.depth() / 8;
    -      int depthIndex = point.y * lastDepth.widthStep() + point.x * lastDepth.nChannels() * depthBytesPerChannel;
    -
    -      String str = String.format("(%d,%d) %d", point.x, point.y, (buffer.get(depthIndex + 1) & 0xFF) << 8 | (buffer.get(depthIndex) & 0xFF));
    -      graphics.drawString(str, point.x + 3, point.y);
    -      graphics.drawOval(point.x, point.y, 2, 2);
    -    }
    -    return image;
    -  }
    -
    -  @Override
    -  public void samplePoint(Integer x, Integer y) {
    -    samplePoints.add(new Point(x, y));
    -  }
    -
    -  public void clearSamplePoints() {
    -    clearSamplePoints = true;
    -  }
    -
    -  public static void main(String[] args) {
    -    try {
    -      LoggingFactory.init("info");
    -      Runtime.start("gui", "SwingGui");
    -      JMonkeyEngine jme = (JMonkeyEngine) Runtime.start("jme", "JMonkeyEngine");
    -      OpenCV cv = (OpenCV) Runtime.start("cv", "OpenCV");
    -
    -      jme.attach(cv);
    -      // jme.subscribe(cv.getName(), "publishPointCloud");
    -      Service.sleep(3000); // FIXME - fix race condition...
    -
    -      // kinect data
    -      // cv.capture("../1543648225287");
    -      cv.capture("../00000004.png");
    -      // OpenCVFilterKinectPointCloud floor =
    -      // (OpenCVFilterKinectPointCloud)cv.addFilter("floor","KinectFloorFinder");
    -      OpenCVFilterKinectPointCloud cloud = (OpenCVFilterKinectPointCloud) cv.addFilter("cloud", "KinectPointCloud");
    -
    -    } catch (Exception e) {
    -      log.error("main threw", e);
    -    }
    -
    -  }
    -
    -}
    diff --git a/src/main/java/org/myrobotlab/service/BoofCV.java b/src/main/java/org/myrobotlab/service/BoofCV.java
    new file mode 100644
    index 0000000000..b108b129e0
    --- /dev/null
    +++ b/src/main/java/org/myrobotlab/service/BoofCV.java
    @@ -0,0 +1,431 @@
    +package org.myrobotlab.service;
    +
    +import java.awt.Dimension;
    +import java.awt.image.BufferedImage;
    +import java.util.ArrayList;
    +import java.util.LinkedHashMap;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.myrobotlab.boofcv.BoofCVFilter;
    +import org.myrobotlab.boofcv.BoofCVFilterTrackerObjectQuad;
    +import org.myrobotlab.cv.ComputerVision;
    +import org.myrobotlab.cv.CvFilter;
    +import org.myrobotlab.framework.Instantiator;
    +import org.myrobotlab.framework.Service;
    +import org.myrobotlab.image.WebImage;
    +import org.myrobotlab.logging.Level;
    +import org.myrobotlab.logging.LoggerFactory;
    +import org.myrobotlab.logging.Logging;
    +import org.myrobotlab.logging.LoggingFactory;
    +import org.myrobotlab.service.config.BoofCVConfig;
    +import org.slf4j.Logger;
    +
    +import com.github.sarxos.webcam.Webcam;
    +
    +//import boofcv.abst.video.VideoDisplay;
    +//import boofcv.abst.video.VideoDisplayProcessing;
    +import boofcv.gui.image.ImagePanel;
    +import boofcv.gui.image.ShowImages;
    +import boofcv.io.MediaManager;
    +import boofcv.io.image.ConvertBufferedImage;
    +import boofcv.io.image.SimpleImageSequence;
    +import boofcv.io.webcamcapture.UtilWebcamCapture;
    +import boofcv.io.wrapper.DefaultMediaManager;
    +import boofcv.struct.image.GrayF32;
    +import boofcv.struct.image.GrayU8;
    +import boofcv.struct.image.ImageBase;
    +import boofcv.struct.image.ImageType;
    +
    +public class BoofCV extends Service
    +    implements ComputerVision /* Point2DfPublisher, Point2DfListener */ {
    +
    +  // FIXME - reconcile and make a real serializable enum with OpenCV definitions
    +  public final String INPUT_SOURCE_CAMERA = "camera";
    +  public final String INPUT_SOURCE_FILE = "imagefile";
    +  
    +  public class VideoProcessor implements Runnable {
    +
    +    volatile boolean running = false;
    +
    +    transient Thread worker = null;
    +
    +    @Override
    +    public void run() {
    +      capturing = true;
    +      running = true;
    +      try {
    +        while (running) {
    +          process();
    +          frameIndex++;
    +        }
    +      } catch (Exception e) {
    +        error(e);
    +      }
    +      capturing = false;
    +      broadcastState();
    +      worker = null;
    +    }
    +
    +    synchronized void start() {
    +      if (worker == null) {
    +        worker = new Thread(this, String.format("%s-VideoProcessor", getName()));
    +        worker.start();
    +      } else {
    +        log.info("{} video processor already running", getName());
    +      }
    +    }
    +
    +    synchronized void stop() {
    +      running = false;
    +    }
    +
    +  }
    +
    +  public final static Logger log = LoggerFactory.getLogger(BoofCV.class);
    +
    +  private static final long serialVersionUID = 1L;
    +
    +  /**
    +   * capturing state
    +   */
    +  protected boolean capturing = false;
    +
    +  /**
    +   * last buffered image to be processed
    +   */
    +  transient BufferedImage bimage = null;
    +
    +  /**
    +   * list of cameras
    +   */
    +  List cameras = new ArrayList<>();
    +
    +  // FIXME - put in config
    +  String cameraDevice = null;
    +
    +  /**
    +   * list of named filters to process the video stream
    +   */
    +  transient Map filters = new LinkedHashMap<>();
    +
    +  /**
    +   * video stream frame index
    +   */
    +  protected int frameIndex = 0;
    +
    +  /**
    +   * native swing display
    +   */
    +  transient private ImagePanel gui = null;
    +
    +  protected String inputSource = "camera";
    +
    +  protected boolean loop = true;
    +
    +  transient MediaManager media = DefaultMediaManager.INSTANCE;
    +
    +  boolean nativeViewer = true;
    +
    +  protected long ts = 0;
    +
    +  transient SimpleImageSequence video = null;
    +
    +  transient protected com.github.sarxos.webcam.Webcam webcam = null;
    +
    +  boolean webViewer = true;
    +
    +  final VideoProcessor worker = new VideoProcessor();
    +  private ImageBase lastFrame;
    +  private BufferedImage lastImage;
    +
    +  public BoofCV(String n, String id) {
    +    super(n, id);
    +  }
    +
    +  @Override
    +  public CvFilter addFilter(String name, String filterType) {
    +    String type = String.format("org.myrobotlab.boofcv.BoofCVFilter%s", filterType);
    +    BoofCVFilter filter = (BoofCVFilter) Instantiator.getNewInstance(type, name);
    +    if (filter == null) {
    +      error("cannot create filter %s of type %s", name, type);
    +      return null;
    +    }
    +    addFilter(filter);
    +    return filter;
    +  }
    +
    +  public BoofCVFilter addFilter(BoofCVFilter filter) {
    +    filter.setBoofCV(this);
    +
    +    // guard against putting same name filter in
    +    if (filters.containsKey(filter.getName())) {
    +      warn("trying to add same named filter - %s - choose a different name", filter);
    +      return filters.get(filter.getName());
    +    }
    +
    +    // heh - protecting against concurrency the way Scala does it ;
    +    Map newFilters = new LinkedHashMap<>();
    +    newFilters.putAll(filters);
    +    // add new filter
    +    newFilters.put(filter.getName(), filter);
    +    // switch to new references
    +    filters = newFilters;
    +    setDisplayFilter(filter.getName());
    +    broadcastState();
    +    return filter;
    +
    +  }
    +
    +  @Override
    +  public void capture() {
    +    worker.start();
    +    capturing = true;
    +    broadcastState();
    +  }
    +
    +  @Override
    +  public void disableAll() {
    +    for (BoofCVFilter filter : filters.values()) {
    +      filter.disable();
    +    }
    +    broadcastState();
    +  }
    +
    +  @Override
    +  public void disableFilter(String name) {
    +    BoofCVFilter f = filters.get(name);
    +    if (f != null && f.isEnabled()) {
    +      f.disable();
    +      broadcastState();
    +    }
    +  }
    +
    +  @Override
    +  public void enableFilter(String name) {
    +    BoofCVFilter f = filters.get(name);
    +    if (f != null && !f.isEnabled()) {
    +      f.enable();
    +      broadcastState();
    +    }
    +  }
    +
    +  public List getCameras() {
    +    // Get a list of available camera names
    +    List webcams = Webcam.getWebcams();
    +    cameras = new ArrayList<>();
    +    for (Webcam cam : webcams) {
    +      cameras.add(cam.getName());
    +    }
    +    return cameras;
    +  }
    +
    +  private ImageBase getFrame() {
    +
    +    ImageBase frame = null;
    +
    +    // shutdown video source if loop and has no next
    +    // FIXME - second param image type info
    +    if (video != null && !video.hasNext() && loop) {
    +      video.close();
    +      video = null;
    +    }
    +
    +    // create a video source if one is specified and null reference
    +    
    +      if (config.inputSource == INPUT_SOURCE_FILE) {
    +        if (video == null) {
    +          // file sequence needs a video
    +        ImageType imageType = ImageType.pl(3, GrayU8.class);
    +        imageType = ImageType.single(GrayF32.class);
    +        // video = media.openVideo(config.inputFile, imageType);
    +        video = media.openVideo(config.inputFile, ImageType.single(GrayU8.class));
    +        }
    +      } else if (config.inputSource == INPUT_SOURCE_CAMERA) {
    +        // camera needs a webcam
    +        // Configure webcam capture
    +//        UtilWebcamCapture.CaptureInfo info = UtilWebcamCapture.selectSize(null,640,480);
    +//        UtilWebcamCapture capture = UtilWebcamCapture.create(info);
    +        if (webcam == null) {
    +        // webcam = UtilWebcamCapture.openDevice(cameraDevice, 640,480);
    +          webcam = UtilWebcamCapture.openDefault(640,480);
    +        }
    +      }
    +    
    +      // return a frame based on video source
    +      if (config.inputSource == INPUT_SOURCE_FILE) {
    +        frame = video.next();
    +        if (frame != null) {
    +          lastImage = video.getGuiImage();
    +        }
    +      } else if (config.inputSource == INPUT_SOURCE_CAMERA) {
    +        BufferedImage image = webcam.getImage();
    +        if (image != null) {
    +          lastImage = image;
    +        }
    +        frame = ConvertBufferedImage.convertFrom(image, (GrayU8) null);
    +      }
    +      
    +      if (frame != null) {
    +        lastFrame = frame;
    +      }
    +
    +    return frame;
    +  }
    +
    +  private void process() {
    +    // process the video stream
    +
    +    // get timestamp
    +    ts = System.currentTimeMillis();
    +
    +    // get an image boofcv? frame
    +    ImageBase frame = getFrame();
    +
    +    try {
    +      // iterate through filters
    +      for (BoofCVFilter filter : filters.values()) {
    +        frame = filter.process(frame);
    +      }
    +    } catch (Exception e) {
    +      error(e);
    +    }
    +
    +    // convert to Buffered Image ?
    +    bimage = ConvertBufferedImage.convertTo(frame, bimage, true);
    +
    +    // display the image natively
    +    if (nativeViewer) {
    +      if (gui == null) {
    +        gui = new ImagePanel();
    +        // gui.setPreferredSize(webcam.getViewSize());
    +        gui.setPreferredSize(new Dimension(frame.getWidth(), frame.getHeight()));
    +        // gui.setPreferredSize(bimage.getViewSize());
    +        ShowImages.showWindow(gui, getName(), true);
    +      }
    +
    +      gui.setImageRepaint(bimage);
    +      sleep(100);
    +
    +    } else {
    +      if (gui != null) {
    +        gui.setVisible(false);
    +        gui = null;
    +      }
    +    }
    +
    +    // display the image via web
    +    if (webViewer) {
    +      WebImage webImage = new WebImage(bimage, getName(), frameIndex);
    +      webImage.ts = ts;
    +      // broadcast does not queue the image and operates on the same thread
    +      broadcast("publishWebDisplay", webImage);
    +    }
    +
    +    // publish results
    +    // publishCvData
    +
    +  }
    +
    +  // FIXME put in an interface
    +  public WebImage publishWebDisplay(WebImage data) {
    +    return data;
    +  }
    +
    +  @Override
    +  public void removeFilter(String name) {
    +    if (filters.containsKey(name)) {
    +      Map newFilters = new LinkedHashMap<>();
    +      newFilters.putAll(filters);
    +      BoofCVFilter removed = newFilters.remove(name);
    +      removed.release();
    +      filters = newFilters;
    +      broadcastState();
    +    }
    +  }
    +
    +  @Override
    +  public void removeFilters() {
    +    for (BoofCVFilter filter : filters.values()) {
    +      filter.release();
    +    }
    +    filters = new LinkedHashMap<>();
    +    broadcastState();
    +  }
    +
    +  @Override
    +  public Integer setCameraIndex(Integer index) {
    +    getCameras();
    +    if (cameras.size() > index) {
    +      // Webcam.
    +      // cameraDevice = UtilWebcamCapture.openDevice(cameraDevice, frameIndex,
    +      // frameIndex)
    +
    +    }
    +    return null;
    +  }
    +
    +  @Override
    +  public void setDisplayFilter(String name) {
    +    // TODO Auto-generated method stub
    +
    +  }
    +
    +  @Override
    +  public void stopCapture() {
    +    worker.stop();
    +    capturing = false;
    +    broadcastState();
    +  }
    +  
    +  public void capture(String filename) {
    +    config.inputFile = filename;
    +    // FIXME make a communal enum shared with opencv (and serializable)
    +    config.inputSource = INPUT_SOURCE_FILE;        
    +    capture();
    +  }
    +  
    +  public void capture(int cameraIndex) {
    +    config.cameraIndex = cameraIndex;
    +    // FIXME make a communal enum shared with opencv (and serializable)
    +    config.inputSource = INPUT_SOURCE_CAMERA;
    +    capture();
    +  }
    +
    +  public static void main(String[] args) {
    +    try {
    +
    +      LoggingFactory.init(Level.INFO);
    +
    +      Runtime.start("webgui", "WebGui");
    +
    +      BoofCV boofcv = (BoofCV) Runtime.start("boofcv", "BoofCV");
    +
    +      List cameras = boofcv.getCameras();
    +
    +      BoofCVFilterTrackerObjectQuad tracker = new BoofCVFilterTrackerObjectQuad("tracker");
    +      tracker.setLocation(211.0, 162.0, 326.0, 153.0, 335.0, 258.0, 215.0, 249.0);
    +      boofcv.addFilter(tracker);
    +      // boofcv.addFilter("tracker", "TrackerObjectQuad");
    +
    +      boofcv.capture(0);
    +      
    +      // boofcv.capture("wildcat_robot.mjpeg");
    +      // boofcv.capture("zoom.mjpeg");
    +
    +      // boofcv.capture();
    +      log.info("here");
    +      // Runtime.start("gui", "SwingGui");
    +    } catch (Exception e) {
    +      Logging.logError(e);
    +    }
    +  }
    +
    +  public BufferedImage getGuiImage() {  
    +    if (lastImage != null) {
    +      return lastImage;  
    +    }
    +    return null;
    +  }
    +
    +}
    diff --git a/src/main/java/org/myrobotlab/service/BoofCv.java b/src/main/java/org/myrobotlab/service/BoofCv.java
    deleted file mode 100644
    index 36b6d58239..0000000000
    --- a/src/main/java/org/myrobotlab/service/BoofCv.java
    +++ /dev/null
    @@ -1,50 +0,0 @@
    -package org.myrobotlab.service;
    -
    -import org.myrobotlab.framework.Service;
    -import org.myrobotlab.logging.Level;
    -import org.myrobotlab.logging.LoggerFactory;
    -import org.myrobotlab.logging.Logging;
    -import org.myrobotlab.logging.LoggingFactory;
    -import org.myrobotlab.math.geometry.Point2df;
    -import org.myrobotlab.service.config.ServiceConfig;
    -import org.myrobotlab.service.interfaces.Point2DfListener;
    -import org.myrobotlab.service.interfaces.Point2DfPublisher;
    -import org.slf4j.Logger;
    -
    -public class BoofCv extends Service implements Point2DfPublisher, Point2DfListener {
    -
    -  private static final long serialVersionUID = 1L;
    -
    -  public final static Logger log = LoggerFactory.getLogger(BoofCv.class);
    -
    -  public BoofCv(String n, String id) {
    -    super(n, id);
    -  }
    -
    -  @Override
    -  public Point2df publishPoint2Df(Point2df point) {
    -    return point;
    -  }
    -
    -  @Override
    -  public Point2df onPoint2Df(Point2df point) {
    -    // System.out.println("Receinvig");
    -    return point;
    -  }
    -
    -  public static void main(String[] args) {
    -    try {
    -
    -      LoggingFactory.init(Level.INFO);
    -
    -      // ImageType> colorType = ImageType.pl(3,GrayU8.class);
    -      BoofCv boofcv = (BoofCv) Runtime.start("boofcv", "BoofCv");
    -
    -      // BoofCV template = (BoofCV) Runtime.start("template", "BoofCV");
    -      // Runtime.start("gui", "SwingGui");
    -    } catch (Exception e) {
    -      Logging.logError(e);
    -    }
    -  }
    -
    -}
    diff --git a/src/main/java/org/myrobotlab/service/Tracking.java b/src/main/java/org/myrobotlab/service/Tracking.java
    index d4d2c93128..205d0df3db 100644
    --- a/src/main/java/org/myrobotlab/service/Tracking.java
    +++ b/src/main/java/org/myrobotlab/service/Tracking.java
    @@ -4,6 +4,7 @@
     import java.util.List;
     import java.util.Map;
     
    +import org.myrobotlab.cv.ComputerVision;
     import org.myrobotlab.document.Classification;
     import org.myrobotlab.framework.Service;
     import org.myrobotlab.framework.interfaces.ServiceInterface;
    @@ -11,9 +12,7 @@
     import org.myrobotlab.logging.LoggerFactory;
     import org.myrobotlab.logging.LoggingFactory;
     import org.myrobotlab.service.Pid.PidOutput;
    -import org.myrobotlab.service.config.ServiceConfig;
     import org.myrobotlab.service.config.TrackingConfig;
    -import org.myrobotlab.service.interfaces.ComputerVision;
     import org.myrobotlab.service.interfaces.PidControl;
     import org.myrobotlab.service.interfaces.ServoControl;
     import org.slf4j.Logger;
    diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java
    index 0329c6c707..b229a8ccdc 100644
    --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java
    +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractComputerVision.java
    @@ -1,7 +1,7 @@
     package org.myrobotlab.service.abstracts;
     
    +import org.myrobotlab.cv.ComputerVision;
     import org.myrobotlab.service.config.ServiceConfig;
    -import org.myrobotlab.service.interfaces.ComputerVision;
     
     public abstract class AbstractComputerVision extends AbstractVideoSource implements ComputerVision {
     
    diff --git a/src/main/java/org/myrobotlab/service/config/BoofCVConfig.java b/src/main/java/org/myrobotlab/service/config/BoofCVConfig.java
    new file mode 100644
    index 0000000000..f90c9ba81b
    --- /dev/null
    +++ b/src/main/java/org/myrobotlab/service/config/BoofCVConfig.java
    @@ -0,0 +1,12 @@
    +package org.myrobotlab.service.config;
    +
    +public class BoofCVConfig extends ServiceConfig {
    +
    +  public Integer cameraIndex = 0;
    +  public String inputSource = "camera";
    +  public String inputFile = null;
    +  public boolean nativeViewer = true;
    +  public boolean webViewer = false;
    +  public boolean capturing = false;
    +
    +}
    diff --git a/src/main/java/org/myrobotlab/service/meta/BoofCVMeta.java b/src/main/java/org/myrobotlab/service/meta/BoofCVMeta.java
    new file mode 100644
    index 0000000000..4312731854
    --- /dev/null
    +++ b/src/main/java/org/myrobotlab/service/meta/BoofCVMeta.java
    @@ -0,0 +1,23 @@
    +package org.myrobotlab.service.meta;
    +
    +import org.myrobotlab.logging.LoggerFactory;
    +import org.myrobotlab.service.meta.abstracts.MetaData;
    +import org.slf4j.Logger;
    +
    +public class BoofCVMeta extends MetaData {
    +  private static final long serialVersionUID = 1L;
    +  public final static Logger log = LoggerFactory.getLogger(BoofCVMeta.class);
    +
    +  /**
    +   * This static method returns all the details of the class without it having
    +   * to be constructed. It has description, categories, dependencies, and peer
    +   * definitions.
    +   */
    +  public BoofCVMeta() {
    +
    +    addDependency("org.boofcv", "boofcv-all", "0.40.1");
    +    addDescription("BoofCV computer vision service");
    +    addCategory("vision");
    +  }
    +
    +}
    diff --git a/src/main/java/org/myrobotlab/service/meta/BoofCvMeta.java b/src/main/java/org/myrobotlab/service/meta/BoofCvMeta.java
    deleted file mode 100644
    index 476516ad43..0000000000
    --- a/src/main/java/org/myrobotlab/service/meta/BoofCvMeta.java
    +++ /dev/null
    @@ -1,26 +0,0 @@
    -package org.myrobotlab.service.meta;
    -
    -import org.myrobotlab.logging.LoggerFactory;
    -import org.myrobotlab.service.meta.abstracts.MetaData;
    -import org.slf4j.Logger;
    -
    -public class BoofCvMeta extends MetaData {
    -  private static final long serialVersionUID = 1L;
    -  public final static Logger log = LoggerFactory.getLogger(BoofCvMeta.class);
    -
    -  /**
    -   * This class is contains all the meta data details of a service. It's peers,
    -   * dependencies, and all other meta data related to the service.
    -   */
    -  public BoofCvMeta() {
    -
    -    addDescription("a very portable vision library using pure Java");
    -    setAvailable(true);
    -    // add dependency if necessary
    -    addDependency("org.boofcv", "boofcv-core", "0.31");
    -    addDependency("org.boofcv", "boofcv-swing", "0.31");
    -    addDependency("org.boofcv", "boofcv-openkinect", "0.31");
    -    addCategory("vision", "video");
    -  }
    -
    -}
    diff --git a/src/main/resources/resource/BoofCv.png b/src/main/resources/resource/BoofCV.png
    similarity index 100%
    rename from src/main/resources/resource/BoofCv.png
    rename to src/main/resources/resource/BoofCV.png
    diff --git a/src/main/resources/resource/BoofCv/BoofCv.py b/src/main/resources/resource/BoofCV/BoofCV.py
    similarity index 100%
    rename from src/main/resources/resource/BoofCv/BoofCv.py
    rename to src/main/resources/resource/BoofCV/BoofCV.py
    diff --git a/src/main/resources/resource/BoofCv/basket_depth.png b/src/main/resources/resource/BoofCv/basket_depth.png
    deleted file mode 100644
    index 14773053550348e3d3c492b36a03f064ef183d53..0000000000000000000000000000000000000000
    GIT binary patch
    literal 0
    HcmV?d00001
    
    literal 94310
    zcmd>_h)4-YN;iB7Y3T+j>6DtGQ9(ip1xe|YZU&H$Mx|@$hM|Xk
    z{QVuz%YF8BUhI2a=Un@~*Jr)ceyKu;M~epl0O5bCin;&*MgRZ^Me^?fAj@SL1ON!&
    zKSeqHfVutlAV))oh6hWsvNIPsEf($XBs4ri9v(T}Ru9FUjO*H3#2mvyf(ox`x(TeG
    z=8{B{6$)YfVS|ze;pI}?P=*Ir;NXfS(F-NwLLZ}|)Err?l$2
    z(;KrvMHM@BTeZ~hj=ekg@8*tt-%bUtzMGnwGck2>ajmGRuW$H&w7BT>o%eWn$C0&g
    zy*@R?G4VeYZkBP8AIZXck6g9<#ODvo%lIFv9$M=de)&=L-A`ZhnEp>FnU{c{9WX=5
    zKZ8ZHkn|(KoLcbX@VC_qb04R@R>%M8SaEYjFL=TY4^6MZD|pGOfS(`tO}kXJ8@q%o1w#Q;!3}?4Qu8VQ6GAPf1ejUJqe9n1^Vy
    zXxzgoNuDxu9v->7T9LScV6JYFIuDt47YC*{um2rn4`22WOb#?kJa)s8)%CJdVF&M!
    zRRWqoC>>$?XA9`m^q|^4uOKw^TNuS6oAi{zT7xBBqB$EL)_q51pyazDXY?kCY0y{|
    z==!k8_f1{H0|UU*r2;IxGNS@fgJ!+}G%QY>a^Ij`s;sX+wni4(Jy=Fu+{k<%fSzcd
    zdIGOFTdOATI$mF5=IZE~xmil|x28vays8o;4UDhboi-j?9vm;GYoNFoj{ex4k_<&1kXuq!Wd);CeGVn_OGgwOv)F^$gq8YLm~H
    zB5lG~edmW^k6=#9G;iUS*@=qPv6M{f@$BnH4sN+W`{&aR
    zQ+P}p$Ab5o_g;}hQ)?4*?z|1!(hPjBr-Ig>-|$Y5>!>7}kO7M53QEL>df+j-cbar*
    zr4Q~*fb_fkb9f(84lI-dv8Cmo%=IdO)*5?$b56q5ilI7mv5ObNP_sR)c5*l@{?aVG
    z_Q{gU;Tc)!3G(wXxLV1^*n1mp#r@M$fNEjvsG4;$!xo56|gyx3E2;2J+4m!0m3>$|00%2gIH
    z&qE{4hGVd=H34y?Cm_v1($vf5(UZD=dF2Z^ug|f(elQy
    zajY3>IXG_XP>nc(vCw%$r@
    zXIXcKdZ2EFra1D_Q<@}wkkmFVaHk|@PuIkigj
    z9gg|OUKAM4y6SXoSouuKE*cb~Pk{AC6Ac+yE&ci~(1f;I{?tB-P5U}?XiHjuCiY$=
    z!uv++Jn$usJfjSNk6#wv*ep*&B+F&g^;Om!9I-415S;%RrlT3nS=pA>etF!^O@MI
    zig*gY^ItlbM~R)nAsH@Kzo$={0y2RGGvrtVUy1^Cn%huI`@G)kfFNvy1SLQk8bky2
    zFLbmv4iGe&sWuZbJf5SDGgr*2)nVUGA2#rf-BvMz77g^-5k;wcytW*CY*;sCEL`j4
    zd(3{2%J+z*5|5H+9`1mq1`P;X8ADI$YhNO*Vp=;uK#yE4z4Na#&co6HpSfiwryok%
    zZ-@~8K>{(0qJWY6^Lef#4%?vAJ`(_D?uyD2dAEN2w--y{nAgs$l{%Sn+#9y<4=0p-
    zk&Y78r_X1j4eKqq|7z_}0FjJc-lR?cesSE7i`0BZNG8$kQyflBgnWsOJlJ%$ByKFe
    z_QTpg!0o(z3{Cz$Pm0p+SXa+2SpG)NtABmVDkX(3~pJ*^EN1*$VG3k+}(T>J@Ve=cg@MhMK0&
    zeQ6U?n{V7PPYUFa=~_Bf)%&Vuj7hYTv|9LJoewSpbIz?fS#l2k?QP8S4dDsdaVFRx!|tQJTOB}`*2tyA^5;^S5KJbl+SC-ac8i{&57!w2?oz_|F!
    zFI!yJ8Zvuu1P|P=F%U61)G7`bkcS)g9Qp|FJ+`DA?KSY6FXOW1V3RNuR8z#xqweN5
    zzNT{9(amR#wI}`chL)aMtlF=z+&s5~xwe0;lFl61)ctwiF}=&?ikV>8mwQkGQXfL~j^jQM*6BU#=v#|^p?d5~H97Xf%0@%x85kX|
    zGrC_TVLB6)-ayg^5?c>t1;@Mqx9{S29(O=SR%7ts`;oUzlvX(9*gJODe5JBgXL&^5
    zo#lvRkH!NRx2oQu_d;qU96Q~{!%4$8`k|N)08gms0CrDvN_LXKp$^0AzVT!5f;9YP
    z6`tW1D4<@~cgHBRVu+`}sd;IH#3fCRY$=%St910G
    z{&6uQJV6ck3Ns^_c)my1GDl(c3%5h-&;wS=W5;3#QnUTflI;>p@efwP?(;#mv1_M9E^UZZ2WZ;bB=1VYm#iX
    ziD^r&_4yj-GfUJ&@W*hcqpXx<&6x;7$^s4#N{HRMDrvUk0m`Y|~bFLnigL9(#$U+^m
    zBnuX%xu8TQh_a>-1uV>ZF8YY(Gsu2ze3=PoKCIn)P%H1%?|Wv%EJ~uwOfup~4n*jJsbe!Zfszz^7VIx;-XjIB~=v+5jcSoo2XHSpvO4tRWz3QK=+P+XX
    z91-U;Et`!ZG1Dp5-C!?r;
    zm;FspFiJ_B1bB`we|?IPKcoVQnSDTl0OroN&o7RzU$YC@9}gQ<>yREcU5kMnQ>68r
    z21S;?mm&=P!|GNW^}Nk@TvM5dQ$*PS^@^=AE;bfHA(I)8K}y0OGvR&9)vZD;gP^ck
    z|CQc0%AQdU|PAsa<0%h0ts=IJp+1
    zALfxTkggv4a}Ca%GVoLVy8B2vTft2=SLYIsC~A7;g#g*=O~NBYh9ceHgVJ~>+{WK;
    zc^8c=X=V)<##|atB6k0CB8K1GgN-+0UJL+Y$-3(bIcV&Zd%FKFnSm$JS8WL=X0N`o
    z-yER%T5zdpfm(6Q$cjD=Qpr2%%0Mts6=5P|V{>v{_QYb3d8N{z^vFWnmY6T+(fTCi~dphM^F>c2hrjEw2@4Z@XH0LaV>5}C$y3DKTjVs(0q<66JFal;6J%W0;(FPt+x)7k2x@Fe
    z4n&VnJJuw@WXonYJpulfRtc+-Xm@i1)(VOC66uFjBi}^$AA3dj##Y}e?7;+Ap-D&*
    zn4XFWtxfVvK@yUAW_h(O@M=4mJ>_Ukqk8w7U#^#7rJu+NP{-Gts1t8k`?R!A-~2v-
    z1N_x0b5R~%_%cQbkbVxRHZMCc-(Q9cR^5lLlt)_mMB38-a}^s11pb=~|y<0B{^
    zzoy_`+NJ}Wtpk=al?HH~qRl{}=7fi=eVx|@M!kX5Ci+daMK4Eq7&;tdm?e7ADk`No
    z%qmP^59X$3NQqg?H11>Tc(ls#18Jhn9yanL320v(K)ItmyC^FuJxO?%T}MI-Tb9ikU$b>Qj>f
    z<4|{3(xR4r`^!O;7LaHF=pHQBmRyf<%Mf=`L+;z|=xxFTsE0{u{xJUsqVmgN(O$I?
    z37KwCW2OB05?*-=_ckM@2byMIAgO793e4AYp4yaY(%{i_lHahlL{Dc2x;!6|Q~I-o
    zUQ5o4=s6d~)JBB21oQj+HSr`^=V9Vz+|KBs@+}KMR3PsRUD;m4wsLUR;4^gj{4S0W
    zr(OE;^-aY62VmbnmTUPor}Z`eB#02ZjWFzRZ71H$EX+9`d-3!<9DBk1^yN#e53O^?
    zo{VX&KIVlymPU#F6E=m=dPaHbI8;l4X<7F%_3*6~rP1O80cO$Dk54WU;$b^@ue;g!
    z$ElMnbm@RM_mXVp!!z1#^ObE_zkX~L`@+BiDhPoz#RfN%4cdzpy#KJg{N#1UBcN=}
    zX#_Oj>7#7aPL_F>MMxTqo#(!^Lm$5_@R29d(vn%;z1uQdfyr14GdoX%fP4|!_Bf;r
    z$p#ha(BpK-g`_Wfc`EOkzb|2&
    zY1^ow%!hV$wv`JRM5OS}$^kb4SX_L}%F
    zT;TlJ+apNo!U7w5&nk<~*5e@&&cQ+T0mvW_qJTWO44MOX-N|S$llw*@X6u}L65{Ts
    zF8hQ0_vTq~!$mSK{#))L-*3XmX@c-&oPC~Kc0GWeE2QzgZ5bbD#}YECXiFF|unNk5TxBo-Nc^jcolgX(XN-?x+FwAnY+Ux;{dN9h5+;_-lQ4Rs3p
    zRL~5^DZE6y#jRui`JHE2vJLl_J2Nvvl+tOVRnwuQAq$dVDX0_-H;gg-85yjApE0Zp
    zSNWD5sus`D$u@`cc(kGAP$lCNc!7)l&A8g5_*X~aSx#29!VfZROG?2~7gFrIH3?6~
    zvB|!J5x-aBzl3!FvhtRq<(G2?2JNJ0_Umevx?u`Uo9oA5{;B=*7@uf3d8;|pga4^)
    zBzfS2z{=Rb6stvtqa3yS(S2^QkpPC`(X=?#5p%>k+z{p2If1Ur$W^
    zvq-oIot?Mn^S)S1GVLI8K(FVM8kVZ$yGr3ovA4s(f#=>1(o&I(66mTq
    zhvP;6Iy3Q8;Nso>aSmVwzka%{b4?Be(RO6$bz=@cFQ*hdlg!7%^?DXxxiQEo>eJyp
    zsJr@dj}TX062lI>MVYlJQ(kg|YHa|t-1`StigwGk(><5_r#mZM4v+e+6s<}iYo&?t
    zp~BT*)B$=obv|t_jRNfZC341lJEm)
    z=75%gY$YBXzK7QjUdZ59l9xPIX$
    zp>ZDlIEF8v3wT=CezOvF8IzElUn}~9k&nr5RA7?wyhx>AWrw&V@m}~T61U+d^Z8hs
    z_{*REYo#=UoPVywGD`l)-+udLw>KJV=-KdlWiX|A(>t~{k)aXYxJu$pXJ!R3f0wR4
    z4@+3TBELSsW!B+=K7A(^;^1?u?RxpPFUs_xsu`a7E_pAS`Z>h*9Q+rJ;OD|N_rx&^>
    z8GVPc>Ug@(hUK+gs}dGkv&S{1y@yko4`$@*hpRb{+Px}_rr
    z*l{HRf)rk8@~OHuGukmzbesvq5y28KD&#;fV`EVzc>RTg1{QPxZ{v$2*=!r}u26>O
    zXYnf@gu8>XZFePMgM*En$x5yEToStNx0W~bwxVKZvV~;79Ifl<{YxZ1x%x!?2
    zUt3VMSkM{<ZC`e~enPx%L}}vd;EMwTt&8;gAG6gwccou#j=Tt94x!1F9y@DcnPH
    z>MZq3Z(`m|g^M}=iwLYN1p!(eS(mL5TbxG27h5
    z4!v7`&Z|ouApwb(t^@zPiv*stAkW`9k>`v!r=*9;RG>=HeN{}K0eTYO
    zkY7oJEpA%6{@TwR01rd6_Q|JMdx0un6!GM0`|**CVMP|QM(!TwH=n@!5Yc+A^1D3&
    zUJEu-vG4EmZm=${jj>;}fgebPk(Mc^x|9k(Sj8t3>n06r%`AQ2l$fMskpYtSsQ_p|
    zaJTBO{tyUZUrG6w34%&RbY}n9aN~P&n|l%@d>cBFJ2vk^6jp`hfT4W)fiqq$Pr+2b
    z0PM~VENN7IfoNw_`1~X+nnMea=z`RMO6peMO|Zh=O6tR-;{dBX(1l|DGt{PK(*nERw`iQP7G+JWDPP7uGSB`%Zs5IV_w5c
    z0>vp#{RAp#Vm}sHEkqwh`enJxD^Z=QX|E>W40wAM=zWoW%dr2_evqTav-5&3iua$;
    zKGz9rN2I8u5`;`A>fkL%mgz>}ygy~RYGJHup>q+1*O}=IW`ho;bXOwh&S8pBX&B7x
    zzmMn<6hYdnHabD*^R5MBu+^K(DO}8_S-pw_jm8=lpuaZ@^B=aSi%#fT3ShVGEkb4b
    zF&y$0&o~)nUFlvU*4CX*7}$~#f$*(gVb`Np?)SEge1HPdt14;bU(!1C7|2(M
    znt^GM6QJZ^Uz|PIlJ>8IqX^~e>QGkwcv(V5$uZwDxG@m)PuB-
    z^44r{K1ob*q#XBXaSbvC>_5XP3j}uet)(zOLB-=+DZj~TGdRCCB-_B-jwkylZh}O3
    z1A+QnZ}IWOuEKBeM8u4U;d8YC+3}-M`TXFrIVo=&`^{m
    zLfOJeS&@BVV`&q>Sg@23ze31b0BeI41q~q3v4gs-=
    z_vi$pw4E!2DNH=<_0~#-WF<)J*7Ogg+c2OowyJEB^Gn}e(Q_q&sfs7VT<|YPH$RDd
    zAsC+9x9LAe;ca^#rr~3$X8b0
    zc(7Z2qp;$iVe9i{q#&LkL%iuMQ7~Ctib!J=ISjj$Fmo|zq#J6(Zi{+eOGG?mdQr1d
    z|M@WXl&+vemFdM$3PW*2&%5HVo?Efw>2JWmABy&rsgcB4zUTGN7bo}zjkQ4+Kf=?b
    zQ_l;qUvxP6z4>GhRA2DCfuKElc`L?+V%8Ov;$KyMPFWt4=i~cY?G;O-njo|Gs=$yc
    z^z(n`y?o`zQhQf%zlfPGKU5sTyS_3e-YZ~Iwr?V8m)YrU9-}G85fiwDSz!!BT|Z3E
    zAkR4Nw#%0=0FJ?E))&I0CS?@qBvytzGKHHR2s8zCx+EBTBup%IVAW0lSAM&JjCnU-s7m
    z%%!Vup+Lw>+$bpMk$na9WMiqGe
    z<>i;Jj&#G(B*djfi)_n+QT2e_5sVsmW9ev#l!9D*V)!q_2MbGeIVO6%36vuM(LY`a
    zXcxLmx8>5&agF>f`Serk(36TsfF0~JQC0B;zT43=jadCne|2A16mPvUj0i1*4ZNML
    zF`#IV5gw|sK}7IVtMSGB0rsmr!fxXAW2eK6shh@QCLPAFy?JuU+2pd*#hI(<3PQiD
    zoB;GVF}@2vzN~g-f(aP2=bKx3)?JnMY;bOtb59&)rnh&O`p$1WL6uG@w4Yn`DZR@LfjgJVCf6$TOl`_KJ(jW!i>Xi?&1X&k?LhoX@{d9w3>R2UX1W8k0
    zce!J8>ZvnUdZu_Ce|vu&8q?Vem4h<>^O5jN`5(U&90PlAq||}?a1hDLCGAsLVx-J-
    z?l4ui>J0VvqO#Bsu%D^)y%wm`^5{i6=gT!2YQN~C
    zb;ld}>B^KzH(~Pi%ACCBX+(HSCq%Bs6MB+X2)nWN}&8rc>YmW-mub-6G$3K&^5?^L&%69x>iY#2GM!
    zdV`pTTUYedI+D@ZF~57tkLf~9(8bgLgBX+jTcyFZAh4C}vJ4T>DyTlg&ywt&^p-73
    zx?UFwn!#C1@hR6D{l=P-9?kuzDl`)0AryQi0`8{(h`*51R55w_PZ~=Zt9my**N{%l
    zOw63C^Y%Ar#2*_%QKt|aBBq(|i;h(w30Xu^SrXlujp;XckG5sNIq{ujh^1fbm~-kU
    zq%@#dHxH*a!@6k%V;DPhUKU~#6qy0Y`GoV;TfUJooC(GRKf{F{#<`{A{?IA-&f{C(
    zu=nDbN^h1aVQmtB+tb69eC&N?O^MIHKu0>gBg&-_TsIvYca6N$GVtlZp17FIylMk5^-g+QKc*}Ri+e{Q*>tiVWi+?2Um
    zW9|JE!qj5cQayPoW`}z7Pv(kEMqYg6H*CQF`AybIedXOx5t)c9CR{|2F83{?!ohLh
    zEM#ybg^T19Gh25Tr5kVA`B42BQzU3F|4eul)%KNd`F*O<;V8|AJj|j9(@BFGV;@0I
    z?k>GysfR|>vLunu41rg|qsdTwW8eg4r4Q#m@P8j<-5TNWJ8s#*`tQbR`9YU@`3B|G
    z{lwrhnL9n)QIahV_W6LcjqX-Z7UfNs<@t+t=`kh43SAx7W6=0KJ2(f&X6D20!l-kM
    z{yZ$bV{;zq5-H_;_~J@%?MKof`|i2l^Wg(L*K$^{KmQHm;w1d(9d&8fCCy6@J{Rjb
    zj{8IMJ5&2+@3!+|+9bt_N>8e7l0RV~s3(7{G_S8c1A~PDm71*?7+neFd2F@hJK5UB>6;fVaLd+$#m1`MKBD6t{d0(XKq$tw{g?CRopO
    zdaiDmY=C{7Mn4!@l+5`S+juz*B*GW^I4Fxjz@ItD@Q;J<{9SdPpszilpR1uXH^++T
    z)MJv-oS5C$pndNc8B6-$_zAK=Han!k)
    z{x1olXVW#B{JyD|Bq=rrasmspt~erpXl(!^BHSKj^jUU#DCUy_zE^{?>8Fntw|)Q(
    zEF!etvIH`lmK#=>G?PLix-E_SjRe?2ydBGnQr#!MN%ST3#P0&DgGqN;Ku8?}WfsbO
    zgA3Uz-$-R>3%cXsI7Vb;j6)U%Cqw+b+mk4k
    z>AIHmX~)?TY3E!?pC{AAD0(2a60pd}T`zo1(_
    zhKA_9H+TNo3);991)~z1%Vs2!7j&X~uY|T}2n?eWRb(X?OK~5oH|=!)RhX@A9=-ez
    zcoj3hLVxI$u?74)h`^m^JTKf7`&P<|625-jH_X>}Zy}`1WP~9DZF4_de_Hy0`p^f$
    z^h1$u+aym(X@>H$wP?|^{4ejErUf>$z*c6I^0XvfTQZGTzpdOSjb}591m2WN&h_uB
    ztE_xw5j)t*(o~4QShHSx8_fkOF5DZ0s!-m`LuL=UI1jJy^TTC?fHgaj=ExE5`Xr%7
    zTSQ7N0Wc8F^<I0>KoCYYhLcxn3Z(D6y7
    zQ0eo87!B9J0iVz@0jNbdW|Mtg+5l~;sJ-M_>kIvq1(@Z980-VJrMJTV@X@D3TpeqBbX};
    z+c?sVWK_3jRz>9}LF)~QI%EEAxw(4l(5)A)=3DN$Ngr%Y2#LE?GFC3maxtkyxz>;n
    z=#Zu4p5}hE;P$nml}6}-HijD%&>!C%hYk`?kwS_w7h5
    z-nC3029>P>TmCmf35$@?(j?1@2~Vd7rvV57(r{SZ?ooLo55OKy7x=UCO3>-j9vd
    zLy7somE;F8zJ30^E_c#?YBSg&R4dbWh)BAR!u)EJU&aoIeL-%
    z@a*|*?-`C(s3MSR0Qc-T`r|Z$^DJO(_h>6`|H=R)qYsz9?mxavt?pVUxa5X*;K3dP
    z9k35;{B*Y(CQPdi1HCorbR|luDfr63C^u}7HiBg;j&Nk|13M67l$mj~amz@t_a{Nx
    z=!0d5%pR>bGJwmw1Y+d?SURK^Z#i(e-;^{VBeda$FL$@SnP?e1#Q#XZW{ue1Z4TWHwUH+$hN(1x!7LSzNgaykT
    zAP7kPMa&i0vDiUInW)pfgk
    ztYERlMC_+8L1PUXY5v0AfMsK3!8wg$FYDGg5OSD-pq>`|?*3egZ*;GKb<$Qj-On24
    z*M7#waboExV_%{_mA$Hd)1}x}qdUcelkwFK6(9INB3){^I+dY)r$5vcH;Q$3M736e
    zHt$b%Wxy!1?^28nGO)GyEbz4Gvye5VvA<`Z0gM{07bo4)&@kWXQPpTPIr!nL9LhaM
    z{j6*j8<+39a>-cNNfWPAdf=NcgQNjbPO^*;0vN>(l124k_8yODFZsrW(L#Hq?Zx~%Op@5&(tU6Ntwl$Y_d~SR=rbk7Q
    z-pR;ms;#0lPmvH9^+{^#GtzMMsU(O(a
    z-z;be5qEWBLpxZeBCU85X&>K$Ut!z6(BL?Gcg(9N+L9%5DWCs$h69aPr+N;{)Jg@6
    zW;AYmZt6TWX0}W9oZI9ZEQ@>P@~`F*6Wrdi^zbVxRaD(U(
    z^LbO;<6FG(sSi%f9;zU1e`h&D(SbRm-qZ^rx{odR7cXL(Ue2BJxg-iJq!1ysu_(NW
    z;(ktBMQq1ycz72ZccHC)@ppKfIOcesc*Ssl`4ePzf$a&yxro0T?zUf#Td}wv&IS{x
    z!Pf&>+;XQs$@{)1EJqnIncdw9uT<4NL@wVXlko^MSK-_eX3(eriiELB6W{KA|B-wQ
    zrdtxcMjbaB{6c~s)lHz#EIlCe7@!~-R$9d#IoFEl^ZcD{{t_8vH#ZWFGeY!Vn|#Z9-sWA*r~UgG6K7h>GZ)H}AgZc~
    z1d72<_An=8QFl<>$f=#sxyuaO@r=lb^KL=QXAg%WiC8&0S>vJNvvm?eq=+p5QFGiy
    zS#;-y6WRN!=*OD6>DQVDGBh@cPtSxQnaB}4iL~knS>6r3cZa~Kx;>ve`+3oXsL2c*
    zn?Wq|H;Vyp%|-S3Kck^fvzP>=>s!$j9O|(e{;f;Y#geBI6G#49-IC6~&6nHwe39Wz
    zY#)J=?6_9U*1&BIjheV80It@~aZ3bXT_sCuWXu4|p+jB6`$E9Txrb@lMvNOG^3!ab
    zMYv^PcTF_0rAvEx+l>5g=;W0WTm3TfyMAT_&kvjC2^Xx?zN#$4h4o|CIE7K|l`*F9
    zg0~nafb5=Xb})esWOivaywM}HQNbIZ{{4yWgc}$*5(q-(#R!CAVo>rA0w9vw4*Jmi
    zQ6ljVBtONhWSG(Zc!(-lCDaESFEd4y;EI6~!wi)(BxFlg7qLM21W1KtHjKpKnE-!a
    zd*tl`_G6EU{-ZkaIZ9-TIuFVfy8p%8V5wWa;9zl^^c`{zELqwdX{&vU_CoxLej6spazuzBP`P0oy+0W2N27LYeKizra@k7pcmCV=dq$Fb
    z`Oq^W9$HfnS|>Ywp2dxZ2^6Gk3r!Lo^HSoBQfC;lxF<(aA75JHhUq|3xzL)?$mQmw
    z|2oyq5mmH{c>)aKZXR)ZeJo#z$%ang>fu*UksFWzn*F0RMQ;(p7lzB}sG!{4rV(5OaJy#daMX2UHjQ
    z!Vod5*x5vvC&XJ%=b9}A81(S
    zxb-?$(bP(dI;8`tiVyTLxhDtGF|tYjW|KuJdqlnfvbg~oHn_#nU7B?<=mGB>apz<)
    z;GT~C^F1<6F^1+Z;s=j7Yc;PE5_3Panm(if$|46c30ID~0?
    z#!E5zRLVGCC!cBiEIEjSNlekEmZ5O9nlpO^B;?EQ@KAxz|BZ1>0sErdb=M=+X9$5g
    zy)U57Kgz4NRKRv^XVYO?-Lhum=GrT4gg`Fo4aYJVv#9?J?YgNrB(|#j<0HnH-|#6p
    z^R~5UYPIMS5+mn-`D8|ka5s~#j*qB!q$Mf0!ec<<5ugrj5P0X1pQ^||s0*LbaKuvoc%;7C;AX9y2`LC=$DKWgB=>=_2vdPkg3
    zH^z_TUtXPmfzRq4uAj>rf88C!W=^>KmBPe+A6y$jS-2Q{b|i-x;n-mX+`~zs?qy=D
    zY)=bP<0)u>_7?=Hx+N}4QfX`c8$4%6kd|l&i&$GY1Z^&M-ukFT?t{@y#3C{
    z;)l7=g+;5Tq4}yaW4;J=_p24G`kAzE#6?(vd_h7C)d%)KxeR=8fp&3v@cqUrk2u{l
    zpT1@G_`47;zu7ciCFEZBRvAjP7rj;6>9?zJ7R-6r`uX#`Ysy^#705GqoY(ua-J35P
    z0t0e^3GwQ1HT#HcT=X6@(2~4E=Eu~`^c)vyorbr<9Z@!g-C5q!Gs9cc@N>fx6h_R#
    zW)@187Wbs&wDfV`+8BtRH4&g8p<%I;E5bvh+6=9{OyJrB-EIJmnwN)BTMKl~Dt6j6HQtHGvV*e#_UL6kFKqndUe6M+V3}d(i5VhCbUvtQ1~O
    zdwg8N@y}_!Z$+OJvID?HT9mQ_VjDRi8FyWf?ta3;vO4OzZ(vz7cJV(POxXLI-tFa)
    zI_)@~hS3)G(}6@l4v-#EF2?63+0MO$cS%AyDZ|eZ?($WRW>g%)IqlEZUtr6AlvM-u
    zh6*&2LHWJ_iqMxzP%6K?YFsg|RcRWB-F@F$$~|GLQTqb{*0%wB?JJMY?pg)$#zW1B
    zWNQ|Um3?B#sGs^=aRSw2!WFcU#o42_PPg0o@B}c6c{JSjL+7c4YJynNNxDiSZhuxZ
    zkrGjSvze)WQS$;lPEN;^QG_wQJ}vzFLn+$-1&Xf{QvhS`UuH>mr`gRmyV71om#4N&)zgpk~|&Z8f~<6E;mV`M8q6NI6VKK6+@tBz-i9P-s#XWk}|
    z!a0?=sMMlJtaKWA*w5tj*nfir@4D%MMR)yhM#e>4t@hbZ0b0rrER`hM^fp1Cd>t~E
    z=BDfa7}1Rds+G(l83i1L;@>|
    z_=eZUK|-_D*IVHEnLrMB2?8LunGD?f0pihOUju7`JS|#}hj_<50l%Kd{e{5(>x3pl
    z*=kOpaWMEOffi1LkSZs<2fRarG?b<*Jf{92zwdssbQz;9mk|6FPyv|XKg|%^#%2`0
    zMe+cfukLL&{U2qRhh2Y);v<9qrQ0|alKM!}wEo_S`4WFZhDi2+#M_(uIT`ptf*Jil
    z>nWS{&w?{2CcK0{+Kh694yrP{C!%H7dkoFSwtEw)rE%P=JLr$?DLaiiBo9cQ;HW5r1>g{WO6d&Y8T@deIwaUr)Pc
    z>FuvkR-~$G{+47h+hRql=+&gfI+`qC3DT128kD<cQVP
    zVV%0oR`l7UhS5e!6LdX;B>H{+^gzfT{fFipI>|C1;P+od0fVcIGo$7?YNDS&<~b}J
    zDpi-Mtf=#&G3nnVgI>6vg1*ATk7A}-Ef8X{tKz_4;rH9fms$0JTsGtZn{%ID%pd50
    zjNbay9Xb5EZ`oGUa`j1C&KLhW`*&m-Es;gUfR8AeU2S~v=c;|5XgY)Cm}S~^JY4kK
    zJ$m;DyN9WYqir+w_Z1gX|0-S*Q<{8)b
    z_ihTE$Eq6sPv{KIzbEP;x)lE&L5-lf{mb9gBRmd7Ip(sSJx=k1^qE#x|4zdpHFCl#
    z{|n!+?todtGui*8k8B7)wrEXSi=8dk@n=1qqdK<&3@$*$Svzq&7nCcVND`n;*<+P|
    zD-(-t9IQE@E)UTaN=*AHNhlnKaklA1#&%(e2su%u_1FX3D8reg6VV7O$-oP%f<^yG
    zODO7@EQ&F#L+Xpm{+dR3eek}ylbR|`sKsqI)lUKHnf7U>nRi$8wl3{Oay@Jj9miz3$1u{yxT$#e92nwnL&8or9hEuM7Q;
    za(~Kf6kpR`NRp^M$9AuhD9=6v&GhX9HGPWLHxZ8tDptT?J3ytbUR
    zApdGsJ)b_W*P8Jc-W5do@tF3D{g8`>2lK+S1@qi^Hd=#yVw7kW5TL&!gDYEp$F}AF
    zBOueoy8B4ezp2mW^*)Av&;Sh7-Y12U+vV
    zIUjh$(*g)udeCO*93v`~_Pkc>H=9Glq?<|tue>AUH6TkvtZ1)*#w}eQ?N?A`s4@_U
    zMRTxrk$`t2W&kr$LBwa%C;LIJC3F6FR;G?#Jy)mR@J2o
    zf5$MbBus^(Qw_7J6W05K`NA$*zwO>1T6I#1&_4$!o-J>TW8?Lg^v`t{^M`l}X?^B_
    z0jur!-bYiNl_`K@a7eJLnrcO4efI;_If4xF9sEEe{_{4v0{?9k3tFiVY#YQwhloYa
    zk3TwoEvEVNV;Bwo-(QL3xzK2{e=UxGW93@nEq>FhuKr+JM$3R5M%m?O2xgV7!@0E)
    z=|ey6+g$JhuD3iZAS!jNH}@P^7?J)rtJkVf?ZY8KZUE7SV^NuHq&2nL6s{r3{$3O=9*hdCrj0lK3(EHr!e#5{?83pv6ht~!3mLLK#X~!fMSw^
    z1X5z&tH!iFor?B(Z@?n$`6smnDs6?o=IDv4|0_CYh;8%V@aI@8Mru6t8i2GS2s60h
    za~6M?u7u{BKS}i~bQxgz$56OgoxJdW=P*|Qz_`%wvZt_Txfq={n0_6ikNb!nZ~zbg
    zC!OFC!vpi8vTi@c&VCmSIu+Z4{nemTsh^LqNK_
    zLAtv`knRSlr6dKEMoJp#Zdj1|)6yuhfJk@O^74L}?=!PI*Yi8)+~-k3qB8g;d5qXB
    zzhM3EsvBKpO$b3%?0ciTFCDBwhv2YPnAM8y_feL;urRiAr1J{{x(q<
    zkfg)T)TQbFZ^mmut86~=9effpnhT7Z?Z5qXyR{^HdlYUD#{t2pAJ8sUX}w#~VdAG}
    zhalP>-e96m7I>jeaPy^`=W0!Ykw@yif{9AdI})nfgAUar5~45&9DU^J{89lO=2bO&
    z!LJ!AauSHE|KjmD@{t80ZyJ}FkbEIR1^u!tQK?{@mgIDhBoE=s?tTb^T16=g_mJR
    z#MEi2TLDBZ@f*?kS-!DO+g>J%haW_z)Jxy>cK@y#OL9<}c)mDMKrRo0Ii3Fh_RvN@jZhFcy8@5eR({aa;S
    znqAGFjzTZ$f*r%aUC-!_YcJI;l9^+C_SWUc`KZt_Ti0;twz89Xq6<@fZrlxNbY6mFf%AjoUT-XdXS>`bS4?h67{-bEzQO#a^HlIU$>qEDC_X!mpx@%|z6M@va}
    z>8S79c(QTuBF@=~F_o}i_h-u)s3e1eP({`%-6Kn7n6E(c8X6Dc`_afsinV)dec&P5
    z*^sICJGy%2UC-oIvn+pZmK`EMPH3gQp9-o&n>neHKh@`m+rvY$<+!V^{P>H-RPt(Z
    z@kvYmf&#{g3X4B|_WJZk6qSC!j^MFhTVgTWC$@W&h?i{S-vl={IerYoFK1&#wmD|k
    zLD24s1!^f+<5+9dleAK}e|GMT3e9Qa?8F@XuVFAJ1LS_&BNKCBdm00JL=6d~PHbhN
    zY%KzZ2mpS%QZA^o-l9CM&3qO?wundkP%}wp2nz5|rm!8?FQWrH);lSVgE6<27vC*E
    z;?3q|wewA%=df3p?Ow>A$?H62?AZ3hTzN1A7s-I+pDx^E@nO5kGi1kdh|?fc(C9IV
    zu|=F(>yR5T1&B!|AZfd(^${V9YNK1s!*a=d_y5mp6k`%;XXvLX5Rn*pMJ9T}v3p#l(W
    z1`AE!DC2uqM?-6hLK=2Q*OBgxOlv15@rVm<)Gus`_of=AW8c-ASaKpZ9ZPeGexwY90@GPU+}B*-q?fH02R$;(8^a}&~SCr<2JN~Hti*({8f<8*!CetQ2tuJqW9fbejXdrs6
    z@|_>l{uLb34;muYBcDKTLxTnE^7BqzZkUpd?#)w~sgEW#O((KfF_Zm#?z3GmmpS^kCbmJR{KFa7uaGGS2$MI2{lpfI!IvTGOu
    z9y^gN0x(4jbPIe^H_ncS2BHYz)f?{bC97f`t2OR5ub#KQ)Q!
    zfW;?)-7`V`MT?lshhy?>ZzAv1c&)C+L+Dir&MMjJ
    zXh3V^`{#SnaI}d==zu3hE_p9Xp2=GN=^6h8dv2{}0{rHHy4aRR+P7P;FMVo|p=Ql8
    zp$gbE^x%UchW7G;nV4lddvsX6HyQ;SHjG5{{JqINIa^Yk6U&>&po3paiHN-h^k9D?
    zGH5Zd^ZM2n!#VYCC1uFDqg+q;zI5*8j7h}rg0qgz@h{@b!FQ3B8Ofl
    zW>FJFo~rs=!B|OIbmFN~LZ`o1if>-Ll0}+KJjpTPT@oP%GJ1dP-3V%G7Hoem@btR^
    z-;a0r(qz*T0?t?ZKJUI4robAJKYvH$(2}+af-NI=e5EX-Bd9^{e>zhC@M-I{Fs5v_
    zRSvbpUl}gUC&I|fyRRaAKm8eKH1}B~@EM>_v9Z8e#B97s&UMo
    z#f&}#+Uor{3MsoVVZFPx%&pWO<<~t;P^GJ9P+!oe)p%lh;I~Zpx)OKsE<^{av*&v$
    z+Wn13b6Vn+)KdCyg%$itnRJ=J$gw(!-+pnzW7;xII$n{xSUMQa346T}N=U~yG?7J`
    z3#!h7IVw%+uM&u_MR;i%Er;?ub^7X4N(647T8!|!KDDSBqVBAWtu$0aCNE?mj>D$w=Dwb5%M3}{27FjZGs8hyam_Rj%m>SenBCAfdN
    zz?k-obco`r=tbqA^hjauor*wV{iPEn87TxhEpETgmRA?I+S+ofzY7pFj6e%fj%9uS
    z!G;E$94(0>^ca9G^h4LOIYntdPFAV^Klzg2)fTL2XFh$l;$*a>!bbRC(8!?xSDnh3t9ug@f1fFxRGi)DNrR2LEBvNvgLpseLR7N>2zP
    z<%f#mndThxILS2^1O5uw<<2Tvy=AkSo=K8|ganK|!5qIumI1K}I{Ouux#Ls5u~$=d
    zhwkVwn-ajSL1F<5>g+BIN``G2So;sp_MR)vf4ggYs0R6K>!Zr-$Yr)01iiY61iuly
    zMe*t(Ap+Z}K@uDBMhs5@Re%TLTAPr+z3KFa725n{LQ~G%B7~LZ`H*-Uj~DdVZD&|A
    zc|CMGBF7-YiX3ztq;o~2zny#QQhk_KjZw*0!YQ~qIVJvgmvQxmRwvvV9csOiog~MG
    zhVMUpa=R_P{So2tH2eo`1ijk>5}+^>I+Z5Ziv|5#rvywEYvX|bi+oF$HO?aCxDJi_
    zg&!tJ^r#5pU{xTchrV$1Aq}J$0fn=|%#2Nx8o*zdAx30?en}WGQ}PGk-Gt6<3?j_(
    z5N)<(4fP$)tAMTH|60gk+ci_`!y)Q8oCw87
    z^W0C3fBKwRe}4q(l;vFfICW%Y`jtv*`E(DuAP6CF39nfCrudnD6QMfNsg&3aDqyvI!R{U^N6w5QHDI9$B^P-OkZ50J>xRx2
    zJnsYkzaj7hu;pE*AR&;E@Rii_Y^x*voYI^P(ero6mqSJtB`K?MBY;s)XJ+fYL~SVQ
    zS(+1&rwgb?ttla$qZoD2Fv=rw)-MWRl1*2_ia^jvSmsM?SsqUWXgT4^$)Dex|q
    z{h)CHPrb>wY0fX(H~xriGS52J{mBL0H%1Jncj92iU$W2c+iT;`Qg5I4M?Pt^zNXH2
    z{)X4w{jZAtdB{I9!BY`9=gl8#_bvC*GpLuTpa)OOoeZf$Lf-JfQPIQ9k>!Dqg3EQ#
    zC<-IMfrPMMCa-;pmz$zJsRUxk>LSrFxoOa)6M_c<9O4lueE^^nU$GwEt9g1gLUk5N_LJ5-D5X=%86F4C!s6fcHBGS+w
    z1y))il&CsXY1HIbP3zS~X0~#4#m{vw0LgTy7RdKQA(RU0RTt-%&VTbRb%YR&lm4Y`
    zY?g-Cp=CuIObc>1@`-)_nerX^rv~YPD#ZrXsDy*a?CC%;2Pp`=*b`cWlF)c#9tulDaev3>h~=wS`uK|N^GAt(V$iAVD=0NO@HH41P0o;+o}|Wa!(z2{BftIg
    z_L5qy+kfcE#Bw@q^W*1DpEw8{i~Q7!0P6WQ4aW&EMq05mWX1x!D^;QVYJQOg4L&Of
    z#hPK-4U(lQr9Ft_5cyMuk+$c2DB((B!w^d0^N2wu&VNSY=HvYVW2hJL|8g~!NQqS8
    z5ptI>Tp+629y*7_MW<<1M<ZzYgaR}@EV
    zmcVBu_t>kbjr)^y6ZFkXeb~Bii5J2x&Hq4Qamxbl2V
    z8xJYSKcZQAkX!4b>gDF{s7P3*ousf|qCtKuxODEUvP-s_wgfERk*)%CjwZ`mUCV8%
    z>ofAHpE*K$tA}hJvxPp0KbwAAz=Zx8a1?_SF!|(wdSBDDGCy9sIqG!SrhN4J)~~@2
    z@%l*cvr_ohhg7)Y(|;kaLJga5dK6t1A1Rfx%#&adY`^`cpPqw3QZnm-smLFqrU;<*
    zhp#a$TW*OpXsLYrqt=r;cn(x*+7WUifc3_)(t?z-j5Wp&q$iR16k9VmAH3qR&(n38
    z*vaTa_!~}1m3$XtU<7SU^g2^oi
    zKIGVwaBu5JD?$>pzQEdW-#s!|JB;EWLFa!vG=9;pt8=h+;H9`Y8+rC^A)-=4s=a@`
    zKnbPb#aDjmR1icRarxL#T{kh9)Z3)CqTC}MdSDt^F#7$U7Dn#&k5T=3c~YWV99z8=
    zLzK|9Lh^CmZp4mMKh1vc4eUB#SOGZwzJHeiklu4^AaiMo9hT2RGiJj60|~w0|EDuM
    zN`@akc_lEt^Wf{~!`C*j6u%&NBa0Tpp<@Nq3zs@)`F6XE)h$-d3xUzHu!vJ(h^%6aaqR=YK@zWtJnS$ae4w$gxWtI#ebnTdr`3@
    zzs!t5+_9Z1eQBuwBl`9yqtP^z$O44p8!)h;wX?J%_DWk-msq^!yss(Ro@7bJQdQoy
    zW-!TH<)Ha>e+>1J%SL&#rQ!GAs`T2sJN0;N4o^V2mn!AB0u$Ut`F_f&w;Z4c?*1$d
    zfZ8_5w`BihqkkFgO2Mi2EnfPo&wFQ)i#RFZ$zXfLgX*q5;-?BH@y6HS*aqB``Q#UE
    z%phsr!+*7Mf|F@a^YRP6N7~nSfR(Q|Ptmh_oXpmF=-h}N
    z@w+KuKha5|9+V-_h!dD(8RnBC!h^}GqAqI%IxA!EfH5^5WVBza;MD!3Zc^g!cxP=O
    z6NkhhQpNdF*2o{-WW_X+s2IQT&
    z0d1p1o$xE5IMXNr$MZ1&W;y5lX9
    ztRORLiL2DHX*X%&7HejPw9La;qbFLhZ!jN*$U|4WvlcQAAW{jhPv4<-fz#t^~i`oz!x;%{wc2j-;0@*ut(~TMSTxPVJ3>tM58t
    znH#TOTa+Naf+4^f5pJNaz(VN0e0hMbS`~H{-|N
    zWEB1Z-_o9^;a1lhQ_F1KaTO)a=ZS{qK`|BwS}uK19vbsEbZ-AS&p$irb9GPw3Dk+Y;fF^*RaRKOZcuoip9mo8BU@Rw!oQK_lz3z00gf3+
    zhQPRP208F31>!0d8#xkZNzjFpPMwqe>&sZaT6Nk)V_UJbch}$@Wn7-AD`JD2gQtIh
    z|E@llW*&@@W4{9?sbwDOP1Hb9nu@`9G!se_gl2@t;zPGSr#ug8KoQ%_PwQPraB##6%hTNJh9TJH_%}
    zv;!mBdeMYtflNM{bjk@vh-WY*(bceH0EG#j0=^W4Mkk!Dg9AQ9|Ix)b%;=tr5U_dH{h5d|zD>mTl|xX)km(g$BFd2(Coo?vn4ZRXTf`*O}z0TZ;FpAMpS!q@D@CKwN1-UZlm*$q1Cx0Fz$E
    zNI^6AZo3tB7eGA=$Z_B2R8RgUXk1D6Qm(_v@PcIcV;9;QB!sZMyyjs_MV707$H!-?
    zR&Q!Bzeg$tt(?E6yXPp49Y=$G28YcteKcz8%K(LSJzTTRLQ@K?x0Lo}t!?m1W!74N
    za%n{cd;=YR_fgQ1gmQ|!KB|bDU+!uxXfwE!Dfi>6j
    z5D3oUFJ!`2U!RC1mzUh`fT<$gbD#qq2ASu5k+5A9Yrjl}QajH0G}^B0FKc5;Ey=Y(
    zX+gR(1=XPcesahTLp~99LQOMz50F@pSQ+phPNLL0-88BA|
    zf>^Ls`xOgTX%4bMFcmhnsCC?=hVxT`b8p;DaST&M>&b&IfVA@fvXLu5mhogY&?IgY5gqJEu>{rfT
    zRinIaU*jEf7y~W%*@WziCCclA#p=*Pm!Td>fyLdEO_x*3jRW1Kt0s?3{Sw@RN#roWwtQD^UJyRBkPFjtiOMG;z_
    zQl6GN{}6@5W%cc*ZUZR(a_Rp=`J*@+URSC%SH0^QS(@w55!KhIsb8e>7Glu`20do5
    z(`ym3pQKDl4Wgu$s4+0h6vzXb)J7}GeOV*s`pd(
    zZ+o=vRvwtE-!t{w?9rQbV!UW>-|Sch)s~Eilu&?_3(R04oVJUa<7EPnVz+rBv^1H>
    zju0jjq$H1fYKXvZ8k%uB3A#pJJ5uXZD{{ZdPVbrp9O+6J3B_N%?k)=tZS8wy!
    zYy9+6C4S_PpO}Mq*VxlcpeVHC5qVJq9wdAW&L_;jRLUG4JM)9^(a0lNQ}4!a+|fg7
    zrGJ$A=gNR2_(UL9NiZQH-j)IeNFI?|L#b~3LJ}#YUhQ|F5rhu0_U43!_S6;ok*}O>
    zZt%G_JYy(#u~V69oBB2WuwLG$b%UttHfp~EY7FRIBTSIA7PGZ0AGv_Y!BYCgY$Q15
    zV$FaEE$mcqDPY~k|Ev%Os=GKldPwy)z3eO2V|i1r5JB3FXpSm!_EK@82u
    z1Lnw*4d&8N--46(yG(z1g~LrYoh37UeZd79f~mkpC0$~JAt`75%Y2Hz*`Q1R+u5<$
    zaj%E@ry^>4Ya78=nn=kW#Elg)u+8Mi{PUF&sv&r~_m7c&{jL+S3e&Cej3;wF=n9If
    zm$7T}Hh!p?J>KCx#R6pWA)DuS;-Ii6b(Fs2F2)J3vK0#G1$Cbk#6vUXr%>k|V&u!L
    zp7OL(T(ejgF>l3)F~Ofo`?;R6ec}VAvFE@g#}Ndd6opmROKet-rrrMdAaEiC08rSZt!x;^
    zR7j$%2@0;1xe2l76na=aZ8|nA#o=~4A$#KepAS+rK4$GNwTR__|9=3D0s2o_?CW$#MxNB0lOl?}z3hm{jCwdU9QZ(kzX$}_YXr|NvovJ6b
    zp~JR{vCT{-Z!b%!r2?S=sXKm2gO#Qpnx0g$P0|ue!V?`z`Btdml^U&*wE#bFxx{155x@!>}K&oXl*jW!52Ax9M
    z(mm&0n$F4QLU{RazYzv!Sbx*~0~+F|@v^dfB_97W&S86ssPQoZiL3w}L<2(fAFzAs
    z$8c5GBOSl8GU}8788xDfl*SA@6W`wsrU-BQL{7rxNE9;et*&~O$IyeKMtaP1DhlY0lWd1D
    zWUUN?u7%L0R}<=s$S4dQ20o_9pSiQuiRUu9=z{CsS)Rpx`Z{AB#UYKf@5qS*d#Qnt
    zBux6*OnuF8c><>qg{~>_$54e64vsTY#+exHPa)V&wO<%plJKJ6oIgIwR|8v5Hj+-+
    z_9Wz31>hz!T}^@)uVM!__{ofL3Uk?P{UaUiv}0D_F-C8zo#R!i3;xfwqW1H(oWnsGV9g_$nXx0ld_QpG1BWwhg=ODyid|s1ll@-Hw-rCGQkKDl&eBd^
    zw`Z4*EfS5P|2%s(3>U|=VbBIS0)_NJ;6mv4rax*MCZ7b%mYV;`@Ao!pHIIBHJ1tfG
    zWcta*V?iWi*7jSpH57KUysK+Y{FDxoIw83vkqka;TkxOF9Hn)U+qQ8@yP3~ERC!Og
    zHeA!$g8;hRfKyg9v-{Aes}VQzPU~P@vP6qMH_-<7)-RY7c;z3v4h4Y`lR1;^DefE5
    z_b`nzpzHb@&zPmZ3UeYXt^wt}+oxO!RH=UIeAz(FRGk8xpyAbLe?u7o8~rsk#)OT|
    zmTwj6(ni>*mKE3KQh(_PJyjkjh->|Zf@L%Pe<3f~>}d?RQv%;@q7D)ajrT^^!qX_p
    z!XdCS-8aef?yo-%6rlVCQn3m6UK*{Eit)355ryFDqdss*pGRh1qo2Gk-V&*u?cwA5
    zhOof#U{zk^kDwNAp$H?0Uj#u9{(;xknBzo%nzulRO%4ek5by@SGFU%tK>p2d;WE8y
    zFcE_8`VE5Xf)!-g|5LPA=g>X9ds_%7TQW{SxAv!}C>&%J?FV9{UC1M2q*CehE+N-8}P=aGtEB=teCxEe+6~5jYWq=GXg%*r29BtTGfSl|LKa%3r2`&Qy;~p0)GIjTvA@E%#ffz1q{>OZH#iDstRR$ndp#MD6-%3Bpo}){-t!{@`?gjJo
    ztX9?bH#|$eCbx`~35QGlK6-Wr<0*KBBpbU}VZ|E7(ON~+Dy0WE6P~;&4l5-CxeM@5
    zWZcTurL~F`l$P8D{O6^FJ=vk$v*dqZ2)*T=PbW^HVJ0BGcV6c{Z;yltHS8^X5_kn|
    zcuU4l!}0lH1@?kc(`(jHkwxFpZtqBqF$B|nm_9-eRi8ezHRYEBIO`cwKj09U$Etia?wsF|HDD6YVw_iyt
    z$T7DU>VLTxQA<9SX37^J!>l)fZQ!x|@ZzhLh#Oi;wmUTUW_9v}Mqk3p;>_RLL9Gw9
    zaQ&Qa|@!?KPVxbZc8Yj0
    ziu0LB$KQe{jS1wxDwZFlw}E%jFgC6%9o-YGe0TOYI_LVJc^zL+*OQI`rH33yvQyRz
    z+Imb{b!rCP+DXBs+&`jdW7Scc=4pc-*RGlnz`d~!-jlc{?n)Clp#tyqMHLdfIEy?{
    zc7z_VIk!&7HrfO}ywW8A7l9MVPObEj$cPEy;{S%Mq*Wp)pwl+HEbS0@YpK{C$`s25?I7#ZYjr!VT_6NK4@1m=Zv$a
    zPt_t1`e1t5Wn>5C{!8eCauBij{224c^(+~jKBusUNDoS>V3~lIy8ZL
    zPC}seBFNR<8Q;?a4U6N`fkgcEz(;3S>=82^kh$Ny(7-e2=f?>j9We_*nl>w-ZlBGQ
    zQmWiKBFkoG+O+8%JOp1?lE!qMJK?cJ4=H7cFLt(S=Ra>o5yo${6w!HH1W;FKwDy-f97hcgz9gxLPMZi$V=~owTYK0aTe<;yJdsea
    z(CI|#=Xz4Zcu3r5;@Wb-;Da?fM7UlX?0%%HX4C68nwteMD)iXHRnJWASN|orWfEY+
    zwYHLwn>ETk&9Yx>Z=TTKjILXv9indGy#o*+&6v)yJ(IE{b1>iV?SlX2b$U%1
    zMH=D30Y&0hOjVAuLUI=U^y?`=4qg94`&j4Lc0r?kkb@tpY7eHI3DF$~@nX8-Qg!W0a4JLRa!lQ%_
    zUM5c}g<`#gkCKjTy__3v)G9#~c=eKQS`WF3+YN>Q&qPkGk-&QcPWM(66=lGxdrg5V
    z6eH}3dFG}JD%EfK?v1c?Hx(2otcdNO65xOWTSfuK4c=+5Xy6q7H7gPZ>?nyjqSn}m
    z04%fwSNB*LvOv@=PpaQVDUo`72_31o1p~shlRD%{4Fwkc8kq6yaAG4WOrh;~n=Po2
    zsp;yzVlwL1zF1cdJV1c<7W%S_j*a3;`}`{MCS%WlJm<
    zCZHpHI|2h5s=Sb|Z1zl1bJhM5(?ox{FNhRmq_gIeD!7hMHS*MN7zxnd(>VS*ONYyb
    zFN6ssZ1Hh|5z}D`g14(ssl&sY07q%~J$(l$2fxNu_AG5V3bZwM6)(g^||l5VxMUW2?$~pO_TfdNZ3>h!`6;
    zMYAv|e~2UM8uJI%b1hqqf~Jt%muZs)mD1Q7G(nr$coN{ts%3gx<@Ube_igFx)*)s{
    zu=R{UWfbV5TKSoErG)ZLlt}m)SzdREU2^7FrDW~BCMeGW7uYW(36OvXne(l;6n|8I
    zy?|7=xl#o8!^62o>kXE_$v`UHCIX#$*Yl=}T2etx`^L+!fwDnkJaQ=E>=z&b?J|Dx
    z1u#EUxi0&Q6{wi*6Bg>bIG~6JOfLsoQJqN?ffh|%f3J8TOCctI0QRN?fTEs{maGmb
    ztsLZMDvl|0RpSx`)J+snmvxws_s!8{5L)l>E>;ShU(aZ#|)T^uOS
    zHAnB&7#thz#pm#LSu_Om;KHT`f~sDs$(Qug<7MF;6uxZfU+Fo)*0xLa6
    z+keAI8e`{~5Lk(>>3C6T3A^+%nhz!$ci&Ju68NU?qhW8N4Tm1YD(G{liibM@I#ox3
    z)=S;vtZWi{BUo4ApPk)BEXZ2H*xDY1VvAv2$V)lg0PMuQ422c0<>k64&=)x|;w
    zepP1*D(mIPC$hG@6=UcpZl2lgFw?7v;xX?O6;{k33S|@a$KcF=G6QHfydA1V(Y)p=We8C
    z;Jc3GTgPgx0yNlpCtI)H=U&nW>&aTtA1*huB92~*f)W4hQ2shApN=hiOq|{_C;xlI
    zMS^jv0+ZiFl95h%k)R9FG1NRVi=N@L!`5>TM;gKwIFjv`N5TBZGvDMhv9>fYA)AmU
    zG2kx2vgSGQ&CBoF9SbAt0fvCl1><`yDKLUhuPkvwCK2Mz5vz7ME5#gqZjsHLA@wVw
    zR`#6=iMiDO0I|K_s;lpug=m(5uF}#2j{YtQ367wbAnpDE3D?_P6}GOkBaJ(H4{DdiLtsuJ?-utAsxDw==P1m2&vg
    zbI-(IGl*q;wV-euTj$4$m_8XPOm7<(G=XzFIHk@Q$l{E%J9
    z4&DgpR^)($wD!v_J#ef8{QXI^)%C^UJL1D7Hm?4?I0Ml375e_>k1VjcAB)y^^PZiT
    znuG(Scp*Od`WHXCRLx<(_qyRu8J^-56b;6(z+#a&{toinMVm9$hgM%AlMv3idUGd&
    zL_=2xf#=K4CM3^cC^VOJoYpE2H=^jLpKjoZx=GBGq0mX%Nc_8lWnMuFWZ@)MUqs87O-*exzJJ8v=8V@y}|ua|*C
    z8`|x}EN^TuEYO1aPL7JeDk)hwQ|&e@<^2@(`O-EveK3_5
    zMHmu?$gL9q??nRE!xH;DUh(slHJac4D17LzWVWG$ReLxD@^-7xX?7C%U+tJN}+
    z7rG7ULT4RmS0Vu#>LqIunElS8AkP(Qa6SmO7-4-$t@__nyK@X)4b!9WOIi{~uW6H0
    zhxH_K$|JS<4G4SW2lD5v)|b%L{G3|R_U_j(Db28rZ~v_O`2qAdXfTINtwW*$&b9zc
    zY(7uyhU0cpIDHuG4@TIAcxtxRivAFB#{Bg7ilKjAr*EzJ(cbpWtqMpQQgA{_IK;#S
    zi54Z-cpD`##vBs$H;8A@U54&%?d8mAg4WKS%An%u
    zmL2L`w8id#FpRV1B53%@9XPf#vY_yN2P8B~j5bp<^{u8wan4OFk)ccEzV+&0mK`^@)%{%|YqF3d!lP
    z!3Rr?zL~$pmIKs%NCy9L=ymjo;rM3}wOhwNbaJg9=R&0<-up8S_yjxqaqRV?f}vD8
    z+m&rceDI$DpYegmpPXl6yb-{jRUT3>2Mr27HR@^O>3%qWh~L1{4(r8UUY8Vp?5+SZ
    zXFc+5K{gR?64qXmr%RLTzAge~N6`RH9`ip61j6p;Istep!a7ib+yIWY{o<)Dl@+=w
    zE>MLKdsAQY{sUGrKr}u7Z}d#Z+3oWXHgZv6E+uWEqR+4zc&8FZW6
    zjAbV9G6e#=`2NU1xfy(J)fhO?sWVjvnp|-R^wZs3w601eNPQ5G?c`
    z_??_xY
    zoOfxiXu#y@DH^H3+hi*~+ZTAu-(26@+@6T`%)GEY)g3(wU@Y3}IQN)BE$}qBjVJB-
    zT#Meco(!cKws2jtQ4ObvQtBP<7aDPBo{P_r(D@SnxmU?>$Q%`hCW#Kig(-qI$%Yc6bsHhT}AJ%TUZ71f?yYEmf5^gEdsgez0NpPlt(9ho?MTj
    zjqb}I!P6KT^2jf!1~zA+isftd!H21Mg+%Jv0L|2$ZgAA2-sm&hA?>4oI|_f;S0IQw
    zy@(R`Nv4!0bBZKnqPWDQ(e~2KSk*@y{fLbT3)SBgKE)Bf01S||`RUBELM-3oK)d7?
    zPAJLnLm7aZy4{K>DT+xd>vB|G=21~0i#I{CIt9;fzv|2UC3X^=a#2c+2Y}B4Bquyq
    zg6~|Z%PRshi3PQMuWZq}kaEqfb}~fKp#tD~XJaNoj>cGzn}k32e!f|tWZO47`J`o_
    z!Gn3=a{ojFBUJnO3kN;dL{^+L!i0Vr1#XJewZZV1?EvcQt>pxEq!EQQ*JSQwcq4L$
    zcX665A-Y~e!$g#SFSI{{5ol1^)Bn2}u=VnpQ0np$hn|zMgiJXVCq=tgfSu!+;CJJV
    z3-jr&^}IC59aOlFLd^U021D>R1LXazMS<27-liL0%LD-ff1XQjnU5@T^3p5tNPGX@
    zV;CPPt)~Fq7}Gbrm#@^RVn6hM&1|vV&=A0HvjtAg$f4U#K4_OUe@(%bw~P%yLaxQ;
    zXcB54Ug%w^fzon&EUgPvDhullZ#B;!R1Lc(HZj&iu>9_(ef_I?V{Y@Y*X;!M4AO$#IR7U7cPtpxLTbD7hL}=+Ry1HnQPWAuit(DCryU2m|16k>EWL
    zm^lh;cOT)ji!U4DiT1K}4+0BiuJ%9aq7lJa^9}_8{NQzINa@Lb(dos=jm9+^8={+H
    zdI1vd`~<-g*uGD)Yske=eJS%%A=~gqeb9+qqeKdglyYsc#>}1v;FN8+^56@gtN8lq
    z+~lD(QmPgRrCbPqetjx9GDGdv-<{HN+@(65tNNZ`7cqKxM(Rd$uP`+)
    z+04Jtkhj2jl7lhn?d~BbA}r61=4)fcaZ^O#i>^d+YxeJ+%F>ssL_)bpplr8EyLo{}TwMMS(4?@NaSwl03-v=t>EyLbS&s84}a~0T8@A^_L|2u2n4^u%^@~
    zeJstMj||%%Dl=GG^3E0s`nD$}E=WpKJ{|qCzcFYvxV4`DSn6#kPMkQ!&rno6BQ3!!
    zSIb`lpn?6rQNAu^yg4*!3ecY_h*Dt0`3bnaNwAPKI2-a5$e?nm5qR|hj5q4z5FQ1A
    z_vl_l7jZbizev6Zb$$czsU8lQOV__yC*^y(akvXL|$Z4cH+^=
    z2KB+2vxyft1q6Fl__XIyk1^1nZwmNLABH#JVrZ^~*M!#cDhN`F4Kk5p+9_*(RJfZj
    z20iy$a*C4kWehwGez((I3UM0$s0~ft@CAw!Ty?|#VtD}^@v7Wj;m@J_Yp6{>SXUo1
    zD4fvKjEkk?=ZUq@39u>*(P@im6%``}vWMq@^z|rnPe8pkOC^Be<((g2B6-(qG
    zCUu1G*TTZIN#Bm=ih9|J@NSRhw&!NGQg+2eMDW?4tvXfPRbM@t1fF<@2%hcGF$(W|
    z$Pd0=e(=icT_5%hp5i%N_=WmLucciYC|`!+K=D0ELvN742I%8&fdJ}wBQoG_RhM&_e`13HVX000Uoy_42ODQG<7X|oyj5c=@B`#0rl|*@P9)L!m*KCrCX}|zR{Q7>7_)amQK>qFrV*qEAYy^(KhRb=nZKee
    ze8~_RkB%>OlE+brz~3x?%CI@`WyP1kH!^K;bRT(2cPv&ppH9aIb}vs95x2h1*NuU*
    zS>5y!vb9Kf7#3d*PRh{9Rayr&pC5lOw47Fad?;gJ6Pws$IA)i6d4N~mVS>-u^SgIT
    z>t!Vpn73s(f~9U1KvzjgdzNJxs%7qF=!l1cYw3_O>8D_R*h$Clvp^N(f&`Q`Wi+TV
    zlMpx=_iqoD84W=wH@Ho8WjzW4*va4oVN4Ux(s2L!MG5SV*{&3Q1pSYqvwmypVZ-=Y
    z(A`KkNGl-Sr68%&jf8YJY;=c;fC7S~U!}V@N))795Jn?8y2rcs57-aqx^|xP#OJ%$n&<+M*-Shkts$yd3ko=MNyY
    z0iq`l68dV?yEBeDbJ4PV@t^?Nc#G
    z2=r51_ZTGQ4steX0N?0mR?D`ujNr;SOmdIlW&&=_fS>&=v&xQFo_)bEp8;>)?5Z~$
    zc@hy-8ETn9x&qw3@OYNe05~}xr6z!6c;^lg)ST^xSY3hMI!h?ANZgkrImRJuI`gN~
    zZ}8BXAAfH)*tUUfN>)fI@KrX)HGZPJ^=dBt!-qc_OKbQ`)V%cOv9q&e@Y;(DqN%&1
    z|5l4(X6P47yyS^|{H@C_QnhOrIrpWVtn_g0fTsCZvWs%MEiJg{;v$@@$%w{Q#L?j+
    z!277)2wb^FwyUH`QQ;tQ9p8_^s=*Wwf9?6pLbZSZFn~wE6WS9Fg3ZeqN`sA1T>t6<
    zG(+P^oFCI|5L3+gJeWJ_t5hV4G9o(9zhm}^3|3B=!kYp~FMP7=hX`!2dO^Jf=SRJs-I3`V$K4_35UB
    zEsFxjDnW1V)g5-RY8OTr4Qtc)zV2yYn?0yN4$O%T=wof^w&Ky2=?201j_H=U$&f5H
    z_e>aE3ShwZ$Qak6;4$N&{bou;MGs$Ul~4K&pZP4+E@#UwekRWj17;xZ<8vKpwZK4C>2)J-Ie?k4hv-V1Ftya$mbZcBL{r
    zj8y<@QsoI*VLxC5EtPYHu5o|S$v3+2Y~~qM$qC{E*x~DbiBr8`dWEah{8if}_vmAb
    zfXKEfJ}q0n`STnbXhbxNfhG&Q0+Q(GEj#dcNXj&x#N$>6{Kaz?p$+wlC3h*I8hj
    z!Rwnh8A>R~s3Ji=m!!e{p@jcsFchUuasS?EqK+P_FbX`yv8gReZodqzFP>L5WOFu^
    zyn0Rk>+xHpFcf*dx0_(C7S^+1`l=ty#1rtK!<;suK8liYmYfs=!4^FZH{
    z4u3seXW`AM%MX;6x_B-|hTqk*JsAqm
    zm!EWBUQUW)lK>+h(7>6ZJo+YSX5c|fd(0pkh1(*kJTMz`C??jx4-_0O^36+8jT8}W
    z?6$X0s$>KG0x7J$#~u5f&YpAQ2y#wa`UJW(h-}P?8u#}X_+ziAwY2-l(Ew}URIpYw
    zdTr3+ZwcD|yyz71H;L!_vJPJN1ZonEw}1Y=_7u!!;_&~?ZkmGiT_A`_j~Kydw4bhl
    z_IUi)Nf?HH@$Rw^RkGc#LCN~>ftTU^RmWfZrRyd^8INSZBpYZ*t4V~|-8RO@md7wr
    zUC3NAj~JaR&RJ53&ViZY2z%LI3(Pz_P8#*=eODGRC@Rb9D2VIYT~t{|+x&@Ur@+?E
    zN{)bX;fkjxMOHfDl-L)Z3`(H*3Hs@9peQ+U+n?o%+6kdh}(
    zmL)4Uvvuevm8;m~g%XfdfqtlhtKObKAEsW*3*I{HIM1t
    zkS&@NvB}S;rXk@n0U}CK_P82Nv(N4WQ>j&S>eJzEZZWDKQQqYlIqvzIwxhU=8T^-j
    zg}kD|bqYz9N_Mt1d8DC))mwa~^l;or?-fKYpC4;gV|pK;q}B1^
    z5OU-0GZW307ofsG-}LyZ4Bb_zEYM_%@3<@kzH6$5q{KI9LVw67ZTlS7`G#$&?%T|{
    z7A&!ue-#@DB&<;JgzSWfzM{lSamxCazrl&0_v8|%26C9)bl))?luoUx2}C41=oI7p
    ztWa#00^5s^rOT#sjt=*HPM5Y;(UlFRnds~F?|qmz
    z>vh6OiiZJ1hl{0NQpcQlTHKP|EI5V9_S5W3z3mZOs?@(sHNAU8_AxDvjLROl4o)QS
    zf?BnodImeugMZ7JZDg*G`IGOC*mJgUfYLk7N!I@;w`H^`SGeHP@A97@55#wnqq#P6QQ(UhhJKSZlLgyR4N&_-GVjpTDoc{P?wX@`g}}2QH)fBVUt?
    z{-*_W=uow(%Jk-t%As$=$r&YmfYYV^BMeq1H5{CeV5Mw7O!{_P4ifG0bO-WCTfK?#
    z4=<%(ZC8!i#U9ZC@qII#Tz7YsA5@)N-P0EmeJLVTd%$^)Cg=Dm0%pE1v??Tdx^@Js
    zur*cQfLNXY#jLqCE7b>;2aF@2fTdw?KS&-xGy%5`IlG|YOn!_7Tp3)WT8qJrhRET)
    zL$JW;r~#LAGnSDp@2wUWBaY`)03L!g_WhprC>?a?w*lWe>sNn!>*1=H*>g
    z&L4N<5G&;Y|DfCb_kRbpQ$hS49UNph=R6oHO$AVR
    z&q842znHo-fyWng`9L|r9v^t%m)j&m9Vq-5vNmeLAfSA(!w*79aD@$)-|o=g3bOmN
    zWqUyEh81@%t&Fs+M+EBdsN_52KIM{l0^>`Ic*B2W2^*TAATyB7t2Yt3%G)PTMa%ECa`prS
    z?mPBgnF3n5XvdF(SC)1?5
    zueP;X%&utuGxreaaTUNrYJ;niVzY>bSGPL3f>vQ|sF~|cxG^R0TUI~2XrNj{p~ljO
    z0rx^GS40N!UggX|=Bd?6AGT%(Mm{?t0;8Px0nZJS&;qvBmwCdi&tzZ@!`3XA381g)
    zu*eYenAZm}$uYUZj3U5AKB{4lb8kHvha;XKty7L#KT=gOrzvolz~^16#LaDjwlZ}8+9J#~
    zo)u{Mg4&WYy#u(gBt%$p@TI{jmk~t@L$UY>f1JY9+saJwPyf>5=}J%{QKNcd2~?Fl
    z`u79WpKex?xObi&1A0G9K=EQ={d&wPej!s-6(l2yoUR*Pw3T@m_=Otqza8dHvYv>%
    zC!jTECW?MrGY4u!lbRCMusROfGKz9gf@*x&LQjFIauSZ))Sge=$@W5+y}H|@naeq8
    zDDtD|07~|TLr(gUDFSu
    z&$C{>NESlcy0+$WYA0hXk9+^Xl6KAAqAisQ2O+kE5l7Yjz~KJCz)$}X;b4I~QpL(@
    z$a6(KVH#kpGxK6nR!b1^=rRfDyId`D!P34RGCx87lbcX>uT1rq?a(gY)<+xQA8
    z9Fe%;q_78o5Ew7Il(JHBwnUmJ0~mgE8l@aD^^A
    zDH`53K5{&gqy^}m%-enC!Dz$Bj^!)Qnm2{{Xzf1s7bySnW56ttSu;hRo?jH(HezT|
    zT0Ov%<~-E`5_-5}C+{BO(d#>P0WWbNgK+5uz(6kbTrhXjQ;9XLq4E-5<7MVuekde?
    zd-=fNtQfdedn<*Wv2Hl8s`-N$dVWp~(eGV;2e*czD(FGaKql)L4WhBx{Z_2rJdR8Z
    zXu_9l$!{4FA_t`aa?VV)jEnTDw4K4{rf>VF?W4rBR_!%}JT`m(lt)>ZrDp2v0?$q1
    zm|nVz)D~k%($H^k$ABY_Jzy1c;@$7h_T^+b+^YrOMZ4i%5Yq-k+gy;Y#~tf
    z-^rcm)7@PHvK`FMFEYW951+4dc_Y|3EhnB#91Vb+%c62p(6;YZ(L=4x0|`=DzA=x0
    zi*6aVq+DVXFPh~%Lc|+viY$<)n_#hG^Gq;nHJn!l;9WW7Rdu&Xk$Z77PeYc93zVUo
    z2g~lQN=hG|AF8$hAhc1o{6VTwIMV{K-5*0NJfkATM5!W
    z%H7MY>mdbS1|b4}Vi+DvFA8sfy(+iOHC%hK1d3neDCm5I9r%`ulpj{C=gYWOZf
    zg(*k_+$bq}};t~{el`!cE7OogV68q39DE4lIrA`)%MnBx(Mm-X!EC;%2(
    zp)*UPwTFHsM>ko~{>HTMs
    z3u#R$@-S9q=-KUB#P4J+;IqRksEyPp_!7)g5Hq(zGL9+(p(A&LE@i|}D$@rCNdakvCw
    zpst&arWw|7Ay4RWL#Ie#i{Q)+9C5*Ai@ESc(y38Lidm36PfDcVvbnB2eku=T6-Ms^OrF!mWHq6?5AGEVZ47T~aeb@0x_QSF9;$1%Z
    z_6cxGwfNPm`(eB8>+Ges`a63MhO+gy`k=XpqH<}MeQdm(t#
    zy51zpb+>j~41($76}W;?j*YV9Q%7VcI+mhU11P}
    zb|l640fVhw#yFTNK6G&>>(*QJKU0dE$&iRGlNF-WY#o7}(W$G_re%5tn+Ko-%N445
    z^*Jp!q{-%L*WnXw@14B;0U$?+Oo`v>3F{Q&Ry2c+%!9|4lV0$#&LRU?k^4@zJfqde|#KWiLiAgmxmFecv
    zUKImOq(a^vO4o4aj5oqPs)T_2COyVY_e80NTp9>84sh7TxoK$~L()5*<9rXas)aaF
    zW_eHmuFr`w28*1Xjomgw09JKi~IWaY=RSzFLtavj3)
    zrMwh^?Hu*l6C{n?a@NNKiQ;^?h;?Xwj&%iyaTg=5xQMZAdP?46!I8H0nRC_6H2evs
    zBW(C%i?sjw|8jR~15iV@!Hi?=
    zhU*%
    z)L%1y-~0~<^DG9O0|;`b`xd!9W)sxke7YHBqrAna`M@Rh`2SSj22K=Nk^JgL#6d5bGHz9x3X7BK9enb8
    z2M!gnvI7r@kFepfeh+zwNUb>jrlMry&Zc1B%h@dVyTm$RnJ5m;p@PDuMUi(~reF+!
    ziQ^Y3i21!bv;+Il>yZKV%>XO*kJwu^BeDO)=1??i!6Ai1mEnj;ApAwh=-l-r0N`x74=@J&X
    zrl~Os(b>?&%^F8OlpuXdbiCVl)lbki|5f*wAVR(saEqQ}#Vvdg>aj%SdDrSBy45c|
    zmMXq$ge(A|Mu%ItTVy0|(F-Tzyc8`a_)4DsWvu-L?z(KE_W@yti?|@s4mAconyP9-
    zB*klzG|`*%CkP$$TcP?Z578Q)=%nu*lWig$0>43DrCWl>irYqJb0A^rRtwv0KJFqq-B41b)2Srgh6WUqmpt|8oxX3}_pz?4WHe8ekF47t2
    zGtH&B9sasXwZHnF|2J{mU~xT_j6aDow=QlxzZwXYzCTUlfJLYSZT2(+O#Z3|l357)
    z-BAl(;eV9&DalPMCtzxWRv9BQr4&U%*5RHa>1lO|@j`P4YMqB;I}5>j*i(kPuIbYF
    z^kKLiaCxv9_{17@Npw)%{yR<`d&B0B_(=T%7GCQFEewnpg#ebxANRTvYO#SeW~14j
    zySB-PghLlr+;=?Vf7+S4e1E;LI_==z0J1$_PxwMn6RhPG-lCMRZR|rF=0D#YR3znVE1n%8xWdHQHY1Zc
    zp0;mw_Uy0EdmU~ljaCHKOBj2&#}IWSSS{M&B0jx4Egx4PM$UmHbs0wg+xw7yV?Z

    Gf&{_WYdlR?lG9R3-&Y-zW{JYon}h)w3_ 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�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

    f-FTN0YqlfoT~w~7I*`?Ldg*Z1P)U! zOda=IoDm&i?40nRu@G_)fXI*t5+H8})8Wm&!dy}VtEa0IT9T|WMXiJ|#Uy>d*eos< zadv%qaoYFwTEg*iG270+Se`et(~nL*JHPm}4RuYBZQ9XwQ;+wzw;ueJ$*o`9ztfGR zkyWfCq*VL$B3o~$skzBq%q&EJpqvG^frTVtG7D2&Si|{fn7M(g2DS_9x?Nvyz|5Fa zmfOq~rYua_w%xp*op;yAUCvyLoym$J7fcZ_^i8|$qq8=*)>t!0yos|5xazMEk8NIM z`!}JRKu5;2_c1=dhXN!|B7AHl)2?=8ojAQ{x5rK$+@Fiy(4JiN! zgf`Z7Ahwxx)LXl;u~h|8PUM0b1~vptY7kL9nQ=a-O3*Gyt&MRDpLLuwMJvD}NNfxw zCbBp(wjj)qO44nDSxBVGDL_&XX``+q5)o5uG*1vs>Nu*3s6bGRqD|zbfKw<%LJ!-3 zEpcRuhzZ%K%~X_7jma!~(;HC`Ttm50<+{z+DIvF3D^Jrr2|zDV5+(scWFS&vAP|IP z1wCQPAQ&Y`1W^szoFQW}R!jnlpkx4*lo*99floa^QnmrIUTsXKLl=FHXfYuXGO{7g zQA$=7iujTjs%(-IG0KcinS>Q6f@~CP&>+}LY>A^`U*_cqqG;RoBnc#S3PJzU2Mh^K zq6`fqLIjM!2CPFNQf8u565k*{Wke!@l#)uHl;8f+F5pW0Ymk%_gD9v{Y#ntalW~U1 zU@=gosgu-qfQhvtQ*?P|I4?#K0mx2*?5B*hQ9uV`K^=0&)(lFh1``_GB7@M>s$~Y|1wZ8{5utFD8wZG2F}ekiEQwgn!{w>ww$`gQlk*Db!k-uWjR z+%L}Ftjfpq`gi?}{WAYjK7Qt^4>)@h9^Fuy=#|%#U?&JDs3fYak+~)TdeCLkf*M$$o6gpn?$Cu5vMp|bV5$378 zDKPOxd&TFE`(g^i*Rsp3JA>JS8n*TL@%LN)CreqEd6DG>QpJO1qlEWeyl!r=Y=W2?)(iaMNY8G`+rOi?|Gqi< zreFQr_O0*MgTKF8eUmT#gY40_`RD)7=CyY@`=8*L;cAOgZ!ee2B@yOD@m&1}Exr!$ zFJ;rGP5)aL-5b#T>(z}<+`IpPAADKh-w`}$KMKwM=Tv=Eed_UIxPGgI@$Ess?3SNh z?A`(W%5?j^5&kzmz5$beiFPb?OX8F5;>D5`EN9PN=f$_+(eJ?R|1GV)5g-3A+Bc41 z=l8PyEjRris z4JGG`d67M~$C^S}yFK7{s-k82Jofj2f6)y;ZtCB>+U^U!TiNSj_Id7Z>Ex?$m_r}P zpZ1$)_0ob&-N6e;AEkO129NCB&(Qt3G*!IXm;3J*)gO)KxAViVo3|ZA2d08J#PvLE zo_E3PXxFDPuP0%WL2iqcSHO**XmgN$!g4|Nvey^gY{Q}MYLN&s zkiL~3F1q%(j~5-Bw2iEf@#@9)?34cN&t{h=7xPaqmSNsUN|JXpo75-w(yhDy!s~~B zV?W>AqAmenxmAjt+i=!xia0khw{c>1OTdaIQ63N@TI_r5^&Ha+?1vfO%+0`+R5EVX!oQzFAzXXUF27p&z?+*L9{GYB8h`vE#lCsYA=?OcKOKGuWKj zW^wHOlT2?K_bTKk+vR85&g6r<8jnJ|zfG5I{VYYyb3bs_`^@AO))lsG%k7%!;)R}_ zEjDJMJn%F&%&3fgkI(^OL{!QNrUV5c8$-D_MNy#U!U3fv?3sfx=nQy^KvtOy2pb|W zL>9g-WUy=Vg| z^?mA?iH)&L#D?Kz35dZFFd`5EB)~Rf8WI^41RzO>icCnt@^Vs7BASE+vP`4|FvJEh zM=3EwQekFh&n(OVJtnZKY&}~_AVH9syj2ELv($6kfA66g-#2alAM5$1{lspy$nFhC^XJ`feRp}=6~A(8=cw&}yI#IgX1`#@Pgk?QzdYL4@h=wl z7mMZ9>Z*mT*cu1y$Odht>TrK=o$uTjr2CIzIrgv~NosX8OV?Tccw9^TAvgCj|K_b> zIW=HlwUX0CnmgPZ@zJ^bw?9e`4gam5$!2W-@VjW@>d{;CG}dcKB0v!&ax{L3bWm@d24Phc$Mq>lSRBZdGL95s|wl8z07?55yrR0?4}=|HUH7) z+r6^-o3HO5ov;7nKR$lw2ETUa@SHFH+3>T>PWBeBc=qv;96X?rHL<4a05e>k6_>_k zx8k^+!=HVTrbNH~4fFYa`+q#%947zG2cw<4=msfQOpNMan(65keD4d0O|vmuEib~} z+2!^2Kl@;FL*1Rh;ChX#D~K7IDViR$7Nz%HTltB7a2GqOkLL5~b(x(I$% z)$~`}z31wybbjc{EPs^`Z|VLRRt?QJd==`RSIgSX&MGO= zY*q%@$(hS4V2&9Obk;-O(|VoF6PS7JGqHEkM`)J141{BEV-^>A&n7Q%m=QSxQmB>? zdzA))G$CzMj~I;hWPr8OjZq4DxhcNSF7a=zHuN8=z=YQLpUPC+%@PKmX$T++NSG7c0B& zV{249k=pLxo4x(^-+1NlZ|xmiJKAthXm?DxqRnNxt|9iSzKhtUbqliwDN-g30Vz?7 zeT^*&fg!$?!(mZcE)>_>?Pa~qkxI{Y+k0l!xS-FdYTrQW}7hN-O)h>(zZ);p`j?&c;4ytOhJ2q-4 zVow+l`C?EPIiTFlMRLQaUth^&aDVAWbo8Dq|*29jmS9TjRMQH(->M!`C? zWW6&P43&39mYklGB5_852t=r4ltEFE05KSH1v^i|U2kj1fSmPBzPA{V<1hvm=Yt1P zidqO%F)bvu3;>=@raO{$Q}obUG`SmOF3lvKBxRJG6b-3`S|utCkt>7@I7fK7n$9sA z;3OiVebBZiVMc=qR6tFTveywoW>}3!2i~w_Fl-D*r;!DdGC*rn$1!T`B~gRWQ1TA5 z(uq+5DiEyl7O2Hw`gPu_F(o;)hP@?bHq0gAh?EJH1Q7w52@&-#>q4l&2B^fp%xOz2 zX_GL4AqoVFs0lb}?6Gf2l*t&Iu`7%c5`d60VCL*F^D#*Z5y@thz4Rp^5(B;jrS#IR zLz4)jB@&@Nc1_o&NC>DDMFVJ>DIS9;#hjH$8F6c9W~uOQ$CE0l1X4g%0zi<6NidQ| z7BKwon?FmemXq@i(GZf2K+$0I5@OU=`yNcM#(;5X4Pl0Dt#~CXwrnC7WOuS^gYA{u z0t|C8AJI|;p%w~Z5pc%VGw9@Nt>`RfO z8+P%?V!xK_#o`V(?+p7thQa@%nY>}#-yN2-!OoBT__msFsvkqoNM##uZ0!gln9awl zv+rNt*g)0}p4==izhvHctkWOB`5UJGmZ>f`(+_6TyD=bi6v^*@}?-vaqJs>w<5>L0qv zyJG%r$!@_a5udh;7wawqJ1%q!Fst`&0e`uW_nY(o`f~69s=q#gY`nYm!$&FoO6qpt zC{Xcv9Q-&Oz0McE3P1D5aPR+B%fGXU`6Qu)4ZDKVD_iAh%7~Fl{}xppDjdp?e$0v*NYSCWCVu zkNf;SxS!9e%k|YqXJ!xdE0cIJF29@Wt90v^@%|V#7W?)3Y<~TMpe>8#DcDc@?IYlC zxxH)jAL``;SpOpLeJ<4>r1*}%^|$SNV+lc3uvgrgdSMoO(zTRKnZjm^jcF6|cN5 z%P=Vn7G=P!5IZm`PPI&_O43NXk|-jADHu(pfaD=_st9e~cjLyF1jd3PYse~sf@Z)2 zQSLVF=DC<6xM2tDXqO0U1G64B3Hty`!|6i9Gj{=e1Msk9sp71NR0guj8B!rwD7QkF zO4nLkrokNS8ndoQGDg|GBe(Y6zWL3+I{NE-@z}3dyjsI1n$?w`Nou=}*Q$lYs_Qp> z*R;sBNopKglO#$~hyY54sbo6L1~;-&k$TAM&2_UuPc&dHz4e}_$h+v%W~papIexi+RzwAZL~INzdohm57-vB< zP|Lui3(yOhj#7UUVo+!k#ZF_F$9mCk<|aS}WD{h)-HB;e#R4+M3{$4&B>gqDiLDg_YM}&}g5(B~2nh|LQ*kOrlM&$n z7!)M32G&F%NihO80IjNbHp_q&<#~U1X6;C5?vPC zqQ_9L+s3d&R=}jB3XI@DQ2{Xkbkthsz2P2YqbVs8BA6sLApk*tO!PTXZfI(OkqzPu z@I-kR@Qyl-ox~nh5dc92Rg8iY0ye^izw-7Y>cx@Cq$FD+WE>nOCt?DVIzu*Bs(^FM z3}J?40ffRN2(jO8M6ybTy*g7CRx$60lCo+GNiZdjJ$KZba_QT!zRc#i`u{&ofBtOi zcAe*8qnXXutakT%pT4;*Zi9OPkZ4Izgeb|9O+|9qidAu!I8LQ1u1YCZIf|X8%1%nQ zT(M(YmMl4L6ibxVQUXW{rbvJQ0fN8z#wzpI^%X&JlTs) z6ZdAtOXKNu-(xnOB5nYYrV5({_Ni*kvI8^L?Ed2PSKG(CS4MB#x>R@OAKXnh0ls*P z>co#fU}-Ljjl)WEJ=(}4yXSG|4RdSLGq5WlZ6wLlydzif-F5qATu=A&pWhijeNTVo zx55>sUwYl2@7UjKY1qprbN!vZ{nyVIALrrE5BGDI2|&1ihW9Q`5`E;L%dE?~FyG#85e{GIuiY@8-pQ5)DZu+D;VZ{|R`4e;*$3~Z|NP70 zw$PVeGBV(cGg`MZy?&fuDOX|lAakF*3GjmOcB+o(@f|*`Rww4dKrw2!Wl=v_o#s`( zbGrH9>FICBdj~H}Z(X~!>HJ`joKJa*AO47b|LfLMxP3d!AHv`MwR8Z*FMnb4huf$B z#_5OGyUky;&lfK1cVuG&HVvg3RvjGa(Rb1VgQF>4GOVkz^`37H+__Ev=(wLm_w(1w zjZ36X%^J)Kk0~VI9p2&Ze#cKyU%x5Z#?}1aRVSDuU_#;l!>DkRZ%QtTF-gVp< zYI}^!d$g?25BZ54mwad2yS}`-^wu?JWdZu?rFgM2@BG*+ri~Zk!3|dwMw505ON%a} z^C$DSetdc{6Y03KSytriLbe$%X4S9#U^cHbEszp;g2a-ueGI$s@i z^WUt`96D!)V0`Y!%3{g}VtjnjtHp6?mm$2pZiiG3vW>F0c@HKN8LC2*KDM>2gCZfa z#f)@-jL*P9>Jqfv9P*m2&0KCu%ts`EAc!bNV+ZS6=h@8hV%Qd_O~tFwGfU>enNB5Q=p6mK}*waMv z%%ZYKlI)qJ2{1iV3J?U5R=r@Mgn?Lzoti8KQ?ICIF%-Qu#04UYY80!0C^eBG-nYEv zk%1I|fhD2#%!mZphK2`M9O1!4@bYx=H(b&U&TB{JIesq2Il zh@nI7h!t8DXw)de1Wkd2f{?UgtpRpoocH^$s?27 zyl?`VG=+rNk*qjXHl+yAD*&oeL=FT?#0yk#U^7ucO+YdUlqbAqWaVFrtYhWYk$I z!Htv=$&uN#zVPgk3P4iyf>VV|`YyIHkpK$8vtE}v1dk+!1&Kd)`MN|_Ekp;AhbeQ~ zTJ23@4rr1%84o0k6>Uryv=AMDC5VKvQaKHW;*D4-GrK#?I-(_$nnZ4nWD$TVF;)wp z!#MP!oA$KNpt3`esln{3=hyr})8!9WF2gi%)VJqtU0pWw=kv!O^x+%p!4}w;2NzFD z{gD~$LGc;iPs?%dMjHTCK&rngw*VO264RnNoGq;ZF0#97PZI1ka!p;hHG&tiH}1AO z-&hy7Y57N-{b1ew`dPaH-7BM`qrujX+_mQ+|C5kyDpZ;mu-*pBp#u70C6AWz8p2>? zKHSOHoA!m@(kp)hx{veff5k<+qW4diIn=vFdCs4H=xz^k-L)G4rpM8}_2J#_r!KB1 z>xr?sddmWoT;E=F-8(j9G`oHU7N6LB^6diu2U|R^SN;S}w(+EdCmPPH4v#uM?C)RZ z=2iFlm*K{LLe-1B{L^^pylTEbudl=4j}3OiVDf=0ZYKFc#2pYz_D=QW?Tdwl;a1tS z(#_UaRmjlkVaYTzt}Q{21+5aPfjp{yBQ7 zaAsu+i3{vL$kO>RZvQy+Uxy#G+HjbcG@j6vO>D2b@ejh_AGC{u@aU848x`y<#-ojq z-SC8c!%gs^H;~mGtea>CnJaUrQYe;Srfzb<=>eRTsoLnaR&MirP-J;NFh!4fM}whs z8xi}yU3W1kDiQ-R+9A*sSQQfVN&8-MCN{ovxyzgzp-uslhM++uCJ{}f5+C#Oz1VHU za5Z_7227j-To6?-?cmy#PTp+yFV9zdOX%z7r14Cij5)V_Y|y5@h}sC$N+U>{)YLoU z`Bj(g{mfwN3%k6!+!;yrTxO2~-j{Zcx`Jw{c7;)$cRY&SBJ_(Spb&#~!Q>;KZ{)6v zQgt22&L*)a9znfrJz0f;P}6jEo3Fi_6~)kHTh_8#(7|%GSS_ohfB;N_R(fAH&eM9H zt(Gg4GZU$p`ogAmmDa17rgKUyk80ZB^)g%4Vh!aKB=vzxgrNuU)){Av>5Sy(-Q+>G zytp`SPluRa0+Xo#D1sU!&z#vT0rWs`gjFnVk z6b01;U{p7hw*W?JAW2ZMDft)&eH=F?bX}X0Q_bL+$j>B5Nn-=4gLP)mT8$||)IMk* z1yqQEGD8kK#+HQ)AY%_Uz(6Ib1Q5ssN=2&-f~vd>Itz_u?~D;a5u*r1>Quaf3W%yj z^@Oa5!jyyqXo3j1CRngCp+_Q8Vy%H!K%`(1g#`>M8AVc5Aoa*4A`3_>+$SSRQj95u z5Tis94cKz-ytlbxnMnJPLeq(^#ja!Dga{o_Kt)!f9-&9PfOHIM5lcjf3W^!Pkia+t zX56x_tpYHCLk7bPMw3kd1WFx900IS8i9{_MF-*Q=(M~mJK zeeMKYuN?pYP!qLoLvF|UFcU*a47dlJR(^Gqg=(2)=c{cu-O7(s{dQlKT|PY-wzKtn z_I!Ujc;4*dnK@pjz1;7bQ6X-EkPN7SK)93B)A{n(pPnDM?34NAXsKU457*fJk&V#| zvpY*-SnuyFTjMSc%SO-I8;{NA$i+*St;@F6Z^JYLthKFm)$7w5MCqn6A0EZO@}R$I z%pacQi*x&XAJV46&pn6hf}h;yK&STlL*^nZx2C0g<5~urcI5yJdV?)?#YpaV>%aTt z{95!^u^>!DsY=~Eb)@s7{t?H`-R1?;*k)g@zJ|{qP#55&mG#s9-8-$Nes3$#NA8_} zl5GI}vCr{whX3RpusYAKp88@i2zwKBqbayNY8_@Bx@1mHFMhN<+_Zxib$2ye|8RCZ zDFy&3*%mkyNu?_H#8W4f}>b*HJ(yC2T~@mpt8JNm;1muL0rC#O%QlD|NkPrJoe z!*P*kxArEZAyu6YS||_~Ryl{0BlDdf`z_U%ZpHP2e)26dH0JYfx&^lH-(O&&Jo8vz zzLvi^a_xZiE`#TU0WfA8Y(-#vZ*MYH*d_`H`~ z$1vT4%OhP~;Moygq}83lERM>t-)*t~Xf)sCY~y$myN-s7>Yjb)e=02Etxsq^#l_mX z$~d9Gt#kG9d-1ow9;bpYJm>m0-v6k{Gb$&{7dVvpQ;4|LyOe-s~qIJGkaI zJdYqyQ}McOpr5%WG&5>4SLU|x0ZaqhSrf{>0Y-_^{d87s;PA$H%M96NYKd~gy@@Lc zecx88m2={!jDl=X0Z4&5Frs~siy(x4paX}Fkx&66$P&U@qevi0m~zzWBEElCjW^TD zI2FCALp}(^$Tq=NfoCqoJvCw~%@T2I_NG@a zzwp9KJ6mrICx^Cs8fL1Z2CNe|CU)QfjK$0|8a4<)`jrMe1$w{$#*Ju5#YP%H-w`zp zq*)M%m;zaojq=Q)*eIt}d%x>5&hxzVnVkres8u;#Esoa<0YF9(1HlaoyR%`Nv^uI+ zNesGdbECMunJ-N>hh=X$m2P8;Hg;eY*5@ZH6N(InilOms0#EIzTo}7Tr=}z`TRxZ{ zy>oif&KF!Tlm;LG1cZnvgvr|Ao~2$9y{Al*MomDF5E7CSVL+}>qA}oEtw9o2Pz@{T zR{{iTK!R#i5QS&mm4pCMB!*SfomG9EdR5Z_SD*nSu&R=(AgDzuEL(?tj7^VS#28b> zX=V+&fwQcJ(1K-U;shpXOd>%v3aJ1q8U?Sege1Xeu(>ImRTkw0N(>yIsrNdi4h2Yv z5fwQ>8DQLl1!P8MG6oBz3_+NJAq8gu9D*T1qikHlEMg);%jD5yh}fd86h#3=Q_`d= z2%vx@#9oFA8SVpL7C z))rnJ7zQB*A_a%wNR^_b1W}O`i~=MkF(iJN@|9tn81Mpr>!5b46DN1*n#Jj*%$hQ9 z-2PHWtzI&S0D|q3D`&dKba(UaeA3-`+70eVSLE}d-#?eVBS}-PuNzq|{eu(Pf%M8i z4#WBH&&>^({NgCZ;lWe)>bC0hs=0E2ybgzT=WC2B^P$1h3=i(7o9`!>nC^-lf7q74 zzwVxg{!duAzgYj;al8(LFHYieG=As@*VMcTxr5L_XX=$*xDl)m2IZ5B(c4cfI(2qP zrl*`!=F_n2M|55d1p^|qB4{q3(}@s~sOIavL9 zv-MNW{|2nC;^+%Li{-(YyE2vjruZ6;AJdOgIE3ov5+_|*D=tEPc?QMB;NgLM|Ap19 zhw#~-PG7mPJ$}8sK1r)Q31dZk_F+}MeO~TE{xd`OF6RHBNgs#yf9)^dkCT7UFE9E0 zi`knOaQR)lbfDesG;iYFD(t<5!{5&D?As4tO*hV9=Rq+idEd;h1Q@2#YOjRn>`NVt zAL9nJeK7JD(|EsihAB(TqCs{VxbUx?u3SDae zjz{@La37t=4o^AR1JLIXE8q)j?nU};Uv0_BYjOV+ZXS-OajRSx)3Nv)f+GkP>2P`c z;FJast_)5+2JQAouze}Zjw$>g$Pl_4-u))0zasNb!1&*wm)B6fgQB2bdk1Hk@n4te z-|ozFX7$JY&PS&BM)qW4MtgeW1CSrc`OU8W!|B?02RHt|8=L!+;xqYV*vGsf4zrhCuzwwhg8LIyswMIo-QC-n&%pjYBbs zv_-hiF$t7{ri$x}boj%kAAS4YH=r7AhD{_=riQc53`f?E^13Q_*MmW~xwG2cd0}hs z^Me;hc4qBSh}k^#hFk`NvFM%{Ono1%lh$iQmx<4`576oZjPFu4;_?!YVs7ffQ(b3O zim7W8My6m=mJNpaD1^G{x~@~{DKln7CRwx2EW`@?YQ3ncMQ;#1OYnJ<`wh02a9FR- zFV>B7uc) zc63pF6wjBEwV^bCmMJPGB>@1`0NR0um?Q!rGO|PfRZK~vG7(#@73QLfXxJdCp`f5q z(+XgTXb>F!0WT>iwt}LFN)m&la2DiA3`! zja&d6Q*uND9w35YBqStFF{PlPi(RXlloXAM0WCC+R1_kRaYm59Fc>Sq0-%~i5_TXh z0s$ID7B*l;s+)k!N`?%vCCC)5LP*#IW`P{YkRk(nXEGZh^(q!ML(EijVNpp4U_coZ zo@Et)z$jv3!_JT^V(hw*x)g&-Fc7nFsQNHrp)gg*Nd~B#f=N=Si~%!GSXcvSL{&5; zREP+%NHR%@JJo>C0$BhEiZS+GVgP0URrmu!rE2OSwT=@S92gUvkD{VUJH@raOmwa? zLft0A0EX%dYYz=NkX&>T<7w0gz?dqKB}60OlSu%|STn3Fu%=8RiLx-p4nsr)Vg)2+ zul&mG>j0#Q)O#=)AOb3)x2Eudq>6?xLsT&qY>yNTXrY=Z0+LdXb(5X=KD z2vy&lHtRktDMAwJ6rQAXH&Sk8>S4fs)8>daQTM@Y`Te81@UL!foHy}T7S#*UeKH?* zM0Z#=_wq{zWAD62RHsyG*o5(hUNqIa$17WQ`&-?~gYK)}@5Vu|@0y8tyi z)YRt|+7n|Q_IBXY-ty`E_s*W)IvBliaCxN86q<$#tfJ@ukGuZsZHvY{m-+c!{MPTn z4yVsNCq0u)Y?B7`TYEHiAQwMvm>gE+c#Nl4yE*(Hs$PCgWCF{)L)9Szb!Tg#jfAQ+hd0T(u z?6~lQPi*i0SXY1H^zhSe`pdU(WiOWD60GZVcoZw)v2n}e^PiqQDJR3d;l4ACJ2(&O z*FPDp`Y^uH?rvsnpN%c-T&8}Qd>!w+6aL#DhV4B6#n-3z7wIp3ef_am{P8PWAFSs8 z!TkqUy3y-&xozxE=CUogIUpWNUdWH%3jgu1h8GO}kvDl6>HYW25aOG!w)X@5Pv5kA z!F=Xp`R2a!h1s%X3rN{$WBXE)^X2T`A*A1I`dyEOA-l4<6QQZMdUfaL6 zvtQ)9z*}MmLTg+d(NTT+Lw>9}D5^b(-F;l_aQ3{NHW;emx;yjDTcgg1ywUExNwQ@& z8ag0~UKQkgWj;F1Sli93>(KfSzcbhf{*C8LNc!-@zHoBmRyu?9;UN#g#`D+r%9nG$ z1*vZOi}n1ewhyUVt?$8Xl*ZTEOGf&ri}}&wy@wa;vsHh#S|PWiK}vo~r5)L5!LkAC zC`E~VuQ6asfTRXHN&q;}v%nd1X*e(^1BK(#D|QMsFd1fBfFA;f z$W2PnC{^f)1_m}bju;&|$H2ru5MtN$VbR93npDN0A_$>1?W3OAR#3NV~zKDo~uffqznQm7b1^U zdKAN`j6l!u%zr>=Rz@@c9>j$df^<ZtraZ+#WaZkBzrI9Q0kSDOQj zFMBy(%sxEK4TQ<`FpbjM-#&x=g&*$^+`he0XuGYzNkduyULap?Q{BqDnM(tE>33uK zcY}Q%(jVpSxLy2cSsp5Ve59O!|^m^*9%Fz!d^qf$F)3ZlW#Grvbzuc z=nh;z=$fDLPw$LQe#hE_t*sY!hl64=%y*pJ=kCr=diYxV;CY)qlV4iN{7jkvqF8!u zr@-fF@Ls5HK=_h1-__NB*~M32>t8jO5Qa}+;~-Wq_YCl)=6`C_*|2;42l(8t`_)x? z{Il_6r%>L{?`-kgH~WvD!q2`}+`Kgy{UN)5NiT+Cjv;(VZ=3$Cs9rdO@q92J0t{9M z2he|fQ2$t~zg@3yz}_FpZ})!dk=eea{U`LS4)5RXul_3InEm&3_>bk})pYmI zD1FoH{v`t+)9Ihp7Yi}H-Yir`I@@WV-dw*r1G=|$+_#Wrwq&?rhmDYc+f;VCN zUvV#QnQ6m21nn3*?Vhxio)JH?;R^WAna%IG!Cyys9+rO!u6?Nft2n=fvtLkmXh&}q z=^C3arRi3TlH5hC4&m{`<=IEaJ7CJehG|(>-9$-gkRC?4)7(Fc^=a3Pyt$MMa^~E~ zsTIjBQHC~lUEeCE+6XA35(AI}?-7A8#MA{=0yHusb50|+rOO5pxmR$4d2$S7aahOI zJpJ%A_^0jGbZh_S-mM#@yIyVrnx3^cyS3Thrt&844f@%jlhxVja&?4t-=4CBAaTUX zIaySRfL?V3!#!!P1St{-*rHDxZE5Ppwh4QUfgW`APaEOBo1~V=vGr5$b~3kYo6{x^ zNcYi=ec^KJe3qF!Usd_C?}a+G$!f5j%O@@$H{Ggk+O^;c#oKIP2@Mlz-n4nAkDB(w zP6|&`FI_oq%Yg|A8|r&iFsUf|Y>4#&=CSERi$QFmY;9fpb)|})*_uLlSR@%Fo2^%a z*|K@`{_??3W$vPY=FVnCj+m1vbI3A46a+v*A|_HIxd1vw%#0bC%ouWxmcVCf27|%I z$QdGw>=_LaBQZHp1wn(5Ar%N7=>qWxkwH9=V$wgrt-#FTSz!u8L!hLp2?we}AdM=R z41s}sh%mvtk+4=F1U5MEx@mR9oReBYM+}Z6b6t?8>(0a+p*0o@184vZEHRzqN;&t| zlK>^oFqQc1%BLWxKnjS!B#I)n;u5l`5Ji}`NC=e=;KvE6Tr!Gm1m?FpCgw8cQ>ES>r0}wLFai7te zX~ouB&KU_Ur(x<-h?YsLjml1tlWGu1qM%|(9I6GiVAhFek-X8;p#lhCQcVh=i7u&{3=jS|S8P0G(h^P7x)7c!bg?8HA*nrh)2A z`p3zrvV*)b_B3K}ndHkAOUli_4Tj!FEQ3md;6ShR{bmhnV&j>3b7DIy#SZShI2di0 zwlJ4Q-e%5-MNBb|JTm*ay;$jTp{<@+cyzYN?;mftxTf*Kxsh9MF~a5*F_#MChe2z| zXPSB64rl%0TlpPdOfU1zwDxb$WY=Xc?2K{Ou1k?#SFOg7wlqDsOTY62lUTM7q&PK? z-Z!NW#m9Poz~j!Jr{#AxPOL3%FSf#&d~elWn-rhg*)Ck>4YVkoN+P)I@a`k|NAIRf z$^T3-DvuyO>g#QP>0tZ4_wC=k6Fzx+_vY<`4b8VgcGh-}+x0fK&1U5;=TC5GR#J(? zfK5*+X+%I9FHL%lcJ`#0ad-IA>gp%VwM&@?$UU^wJ)O5_4Uf?sf4~15Uuiyu{$F@) zbbk^5%WqcKDF4#OHhz58|6AW*{GqJ)XJ6RPcWs{O@l4)3mJaCS+vefj>VNpn`IT(& zrPp@n8>PoDPWRfrKHfgNdMn=_Zw!qYjiB0)Q6@k6R`}2UQGab}fBy5?nbNPn z3%kgF_*4D?EdC$Ae{@|(U$}MI?Pv8Ab|?7aHr~CDKm4cQa(wyN$mBJ6g4d|E;O-tpSZ zG0Zqo4}(a#EvDWTu?p=&@h9xE|W(emLF1{Q0<9W$ndq)-SXC zWL&q^N0altYp(Fa!f-LbXwf-1J*~g}aMcg(&W$W{)~s`OIC&v9Bly81^aWmegX>*= zzfGHzU)vqINm@Y@Q))qKgM-o>*GIoGyFV>AE*IAk`i^Iq4GO!%Wi#Agb*Xzes|S~g z@>;pox}wgIgiI1e?y-e#<=c8Dv&RY@mB1Q7P?gXyrKVpmyChtIP0tS zo!&ir*!Z{{t8p~8HnK8lbkH~qF4VR)2|~|(Y-3-o7EN09Z68vKl{5=qcap+PIkSE< zb0OQD`{txs%$h*d8O&`l%?93>sl|1_Tvn^LffRs@rOXX&wgY*s?VK9{WFRvzgTk0V zWvkJ3Ro*w}-Fz18M&>735z5_As&B-uE<}5pF{M>{xCi&y|KBOzL1N2-EJ6bO)15F=uZ6cBQV9v~pJAOCENX%e7uq85R$XaxcC?k^0hk1rUvjA3L< zVZ<7>7IN?-@R=>hB}@Q81hGSHRU=>mP|y~j5di_#w1hMRvA{V9dus}00M5CKV<{$StPL8<{!j7f;X7^RDGl~kiph;_YK ztgL`xD;=etQL-EgK*P@Pn6oj1q|~)RV0T-`c_jCO)0es6{w;TVFD1)J~9OK zR!xQp@Y(!XRBDWK0wVDMpg~gTQV@Zt5L7#fjii|RkUEhyfsBpYa*6~>B93Dwme8vb z5)%M9p zOv41atIXSAUa(u+mWbR)X-l0@T+?>GT#wJr3c7G^zL?ju;|f7(`t;bk2kq4{xEq6R z&Go}>a0RkY`;fI~>)AB#&6YW7($IWZfPls|U?o5wiVLHj%o%8<01wsBR* zCqftO;t$09R<}HWy*KQOA0+ebcJeW>zihj%ef-|y$_CI2gZCEY^dtA?3if((JH}t3 zk&&7tX;a1OvBUG+mG8pjy9fpQZMyV0wfEA82leBtYk$YSd2jN`=X!bWcsIgPQ&3CS zn(UQLTHn@$!~G}aenQA8_Q1VbP{VLBNf(#o;0Z3i53`rx`Y+o{mwZlUn8|7!hjUon zHQz?M80c$X(HH+Znb+;bpEcLN(O&v5+Vn9u`_ul_@59gkee zOrf`HKo1+wY>elce`%<3hzSb=b?pmWvVCT@_b`UO4y|2K>B)J&5e&?K>cG$?@I-2y~!~q(?`}w zTC6Z$*mJNM^3X7rgv6SV23O+l;e7H{QRxwUcGv4E87^G@s83J zp-m_^VQe6F*dH$+K0d#60;KyOaTiD3qC4{_7+wJgrrJfVEnR8`v=a*jCjw(wv}IOsOEr47Ow&%JbNl31+=60!<8#Sc1u-$?f9Q%#T5kFhq%cjNPiL z>%}8u?^+rI9;jP&7iV$)Aj|INh$a;_Q3%w^y|ejGPb6=6U%eq_HP3(#CV^FEQb1fK znQIT^m~vy#TWb@Q!4C*BmMOrN8k5wig4Kp>5pL_pDjWfC`n8*brv`XB2Zy zLy0CP*Eo)1AVrZ@V%G&}At-hjo;_L6q!L2vqfkT_d9Y%tEe#M$1U6(10U!X>fGbd= zn28}2Mp!9a01|;=voQYLS#s#DbSMEjK!-|Bs8Ajwj*&Z%wbm5D1~&qnqHSYXr6`h; z#!7Jk%uLo8VHAS^YLSd3U`~RfD4TdjHHMOi6B{wfT)<|(hVgf4IM~>xJF`Rr^ZPt04=}z!mFw? zz#{}g&XB$3!pTH=LxZXY2@zsP0*WSuDuhK!290HDiHT*y*n#ty2DU-VeUI$(gYgiH zL|KY~Ot2`B3pPbuWWlsbRSZZ~vZdN#QDCAFl?$~)UC#07h*~-md$?Tt*+nUf<5~Y` zLetxPqt^C4ce*zyue;rO>fWK*zV$D3qbG0AzVfZ7*B!rpX>j*q{dW&f$J5EJ{p*+e zTpP#>+}uUpMq5I+?jD^sD~eeeS4;c!sKC_aTh(;OUDcv$+xcLH8}>4k_tuNQ2`AHO z`Q~7EBf1sC_LOgLS+hm9(0VQBb6N2^FP7)W?!o&-iMV}(3LtojWYYGP^w6%}I$2z* zC$CQq?i9x-t9O5pinDC{3T3aD(OuD#HjMhRjCmT+XlPDZb0^~9mc>U4`TC&%0PgD5 z%l2SWjFK@CE~<2Y5qoQ%-}Fyv{qnUms`rnyf+}tRxZEp>Y zD+%JT&xh?h-^2g$@ADOoKmYmk2;HxL%kQK7p-;y9=kz!K3GXKVbDtT)&)BmoQZguD z$Y9Um58hh-g}2UM`=r@>GuuEKdDFEzDCDS4|KT^}rN(~xIX}J(IuH>MgSAf+7w^2K z|NTGIeeeGGFAO@n{=tt=%Vy(aliR<~6*SFwJMZmfZSWHOnC9R%}dAx6AX@$>Y^G z9;^m&{6~h@w2lw!*_euLv(@?TB%4*3m(4VU`aym^GQ}m?%m>!&AhJ*;51-=i-6QaD z>p41qRQ>H=Ki^)sOXCueu;CE8;ZAkFmf!vsTQoJ?3t|{HHCPi8K6OoEl`EC)AJ{%$1|y-3vhATo}9KNnH)GuNKsQS5-=qU z2{bY4ysehaa!vJxZ3$vPO&n`_rdSh7NCxR3E4OlwEe-MWs!X4bl~0L!utC%sI4KHSWyu$rt&6GL?#P7brDGEieYM#U5n5DcnS zvML3@5CoM7O-ZgGw9c>x3Sc@AG94o*fTR{;BveFD!~{`OR8dJS(i&5Xl2{XBL~IBv zA~n`nF-Qi30w4qcG9V>WW{tWGeU@3vj0DkXp246Hw15V|qCtwCQq)w3_M!uX++wJc1c|6Z zfQV?2OJi~aWDMAZ7#NAz5}{WvK#2$m$QbWvNbH|Aij^$*0z?soAwK(*6z7pMH52eI z%b?UQp{8f$1S$g!dLY1O5jdo0EokTO0Cs9EXinH`J!_7}S+Q3R zGY;OjJ(_?l5QQ`o$~3J>77(K!w%JC>!yPU|SN9DKB~GY4TAkiMM=O#Ij%FxF)kd$5 z%4h~FV=g<{AK2VwGog~#JK5TI{PdhJeJkwzPgGnUE#DZJBi(ucVSjY>X7Tx2nxz!G zNpD3d;~c}~)BfyX`mnUHoxzddM-6R3{)!I(^OLYO0KV=X0K9Y2KM$~1jw~DG;MKRX@tgVdHTSxQP{O)^?h3y04Z8K8(%$og|2#ZbDhKV2)Ok8_@WAr) zAL{OZmX^OjNB<8Z97VfSC_lZdU;XFHmp8gOuyxK|Ee2aw(b{bI)uvMWuTJp|hN=Wz&E&yaO6%aNHH!6LGb_mwBook^0bg1fF@MaTe zU)|Aaac@;s0BcA#b9v4KUu?Kxw>n!b=cH_b*f3fZZ0GHHAWI$3jq04&6kR2N&Pds8KG0p>&N=3plWGsOol89=Il2qYB3P*pU7R*`^EO`$UUgMb`3fKshb3L<@kHed-lArOG17?K2m zneqwP3DO=irW6{dJsM?VG@gg5LqH^a#{D27Kop<`b&Y{jFewp`0~KWz11YA|9|GPZ zJ>yqy-2|;vlfWR5@1n)%_*oJFnkX?5AQGV^VKxjdM;ahFRHMl&8#Sl`WYM8lZ0@5Td9=$#6%NhUI|fCT73ZI)#=%SU`;#Am5~jB8C)R`&Gi`Pa{$?hFRcjrOaP z>fWQ{G8;@c_oA4ebYVNUuTILdQ+n@C%6a|bw7OgKZ!KtA(2ZMpx;I^ssy;rn)e1>n|r`Y#J*2Z<#KN-bEKeJ1>Ypx8llZ$w->l;t8HLhFR zV)f0_cmML+-`+J_*KWM_nZf9@>T4j+G?>~~E*G;gQ65jSpZtb?i@WAY4f#TT_kypp zeRZeY8JX*3jKwn7M^*L1*`imo;q&eSfA0gm(&Hc9r4{p?b6$i64o`hwoIKjx>-l5* zs+S^X;rN8V{?vL8FJHlnJMq`P+}%+AXI}LePQLOudZ5>@X3aW%`6ul)@ULAO`%Qx- zob>A-w)47myckm7m@BIU{-?h`+J^3PpItpv`kUYM(@57pP5YmO(KcWJR6NsKarM); z)BpL)@{0O@@e||qb$35u5#a#!!HOO>td4HpW_%4EjCE{bI)FK*nBvLr^H+b>Zu<1I zUks;Ye&t){0Nv+5J7_PT{S@vk!gx15SD#n+)`xCPPyi0lbh&$;|nic@WR{HVn^;LF$T`TgeGzrCKgeCNif zSN;A;y`75JhFdE?yXPPF%Yk`xkVw*An{7GBb4WH*+d?8u>nH2OAJ1{kn?)g}zt^p^ zRF-B6tzsP^IHN^0RqtncX|rKYWnqd@=GAiw)Kt9cS%sJsnW=62)kzD(9eDoQ8*l92 ze*IwUxqR;mNG^>RXCPT@BCcy%)$LJNAFt|J)#aYayXxZc!}Loe!y-bVQm@(*i6+2ZNdE+xjh$M@%)!EV7 ztetPXtCH006WlTC+i$StAl9 z1&NS4)m{{oi4fTcj0D61<;;;VfHDFQ5j$hN_#$P1Nhyly5;qChuw`$^T4H2FP#}&0 zi-4|E21JVZtZ%A{sK5j!Q$La18Y(>Y!c`=f?6`AnXxC7g0UPg9;}|m-Df_4dz=+nm z%(Fn&f%dUUp}*+6Cmn%knflN)b!&zrv%P6FVF+Q?U7XZp*^~|u5s5$*R51z%5~9Q! z2z!G*Gi8PVkQ7^MR-T&NcDXh{HE1LYMiZi{GAR=RA+UuWO$!++VXUgQUXlUh40vmb z3__#rN}ef*K>&oJVuaLm%XN#)nRl$lf>mp*H<46SKthUL43M6czd&P92%t%U!IVT6 zbR{|$Ovp$a630XX;tf_*O^QxAgJ-|xS*u=FpmRh}u!=s}Qh*{*07pOud!?j+qE^rX z=R_NbIi%QB-O^j|rNQJdDOo5H`sk) z{Y71zF4iaf_^x{}xV$=fF&t)d<;5j=xUQaZbLmob`%4$om(E6m-E8`jEeLcCMczIh z)!_o|arU9lx`}=H1KIm_s(111O&*@N&3mh(A-HSBjYq}a_pIL+`Lxs-RuykNiTJRc zUxD@$=F*qp>R-a~Znhuq-mGE-5N$qxGS#^B;@-x>*#^T#r|}e0Z^;hO}&< ziMR&PsMK4pTzVdk@=0k5TNE_lZmBJC3`UpF{_jTmETxmN|L8!TKlquS+xg8d$=zP6e zRKcKH3d3?Z7!)~YIgXdB`C?UDB!@0$)aN>IZo}pC_4?^@rGf($?=tkB+6LF*sE-d3 zn{3egVHcLEZJnqG=(4OWGehbTYJp|2@njVq$m!F^r-#w4V<|wCh@LqJbgjGqZ!u-? z48WirL5+yUIOmWRuor-&oGT2~1jQZzKn2hUdND~Qs^bJkfgvI-*;!{Y$84F5q7@t~ zZmF><2nkUDk_3tUvv(W-WI!hUp(1c4nGZj zwoPd*CP^e|pMtqVrexeAX~?dO+4lAd4MH30z{g6DS<5Wj@PyFo6Ood6bMIf&yEB956>3 zq0Je&sDoC8gbQDuhL`E95D+w)xsCp*xNkV{P zKxC=N`g*JMqlsU;@vM-gNE1<{2ar?52P7Q3LF+2#k8_27G>UlQSC2C1>0sZ7seO>^ z#QJNSqnUS4=BWMY*gR^qShUmi`r?r}-rODU4KG>2%7)F2?AEBLBewx8rX7|0mHv2@ zO2b*qFe&Z zFaWHo>(8giqr*=0ln|1R+J>SXw%WizU41eda+NterD%dfOD9L(z zxqAQ3?6)2-8dx8mK3UG!OwGf`_y5*AA8pNpU9fz(Sbem(_rc+#_s;CS+u{BzS1*r# z`3r+$+wNIhp1|UfoV9(C_Udotqs3zNyT?bjc#kxQB1-GE|!?)T{ukGgY2luLf?;Gon1^da3f=Dkw$BFis&t2*;Joq;K#;=;~ zlD_mMd;;bxzvK1+e(?+GU1|RBUtjN6gI~J5KXevJOi&{hm#KbKi%or*?1JSSgD!f$ zSTy;2^~Up8CV%$zjd|98ZN0KF|3tB|ko7m};~UfA{*}G`-0cyphPs~W?3B*mx3k03 zZ&eRB%IPhCJ#k!Z%pltKUJ-lQx&`&Y^bu@c-8WZWlF^0^J?IkaIW4jFZne0p-~BpH z#eU&4gC)v)Z*>DIUc5CrrR6`lIJRN1$NK^6cg80NTf?c}_0i?RY}FU(Y`y;4`LZ0^ z7oRWJA-w%wGlb1slgn7Nl|PpSzy$+$nl(Vq!`PW(Y%`m%jJnqLA+?H{D-mD>2uEd*Kd|t5u8WwPPe|`MU8WAx=UFCLO z=H3lT*Hz1=nfDFw5>3p8DN|Z4%xo2*Wj~ns%?-CU?FH7U1DKNyWzIt=3B~}k5RST4 zd;hd~@3B4n?)I@M#6kvyLWH6wDF`5`PJuQcszQ%gA|y+SA02(MKegg_cWMRn1qlTLuxS!#&Zs8JJ$ z27yMURg8eoy59tr&9lq6rjWEtA*D_gP!T~?VoJUIL9j{zg~%~a*k(vztaX$VK?+Ui zqlhv(S8Vysv13IaK?7os&SXPhN-x+2Q9>d#Kq%TN1$=h11P5p&*_51!Q#2?<#0+dK zvCT6xEE*1L?F|}>1`ShY#iD?q2=}2oPR$`gG(ZOAnLNlEYq4NIvE)q>q+TJ2kO)g^ z04t(isRse>nGh62S%`okDJEr0E+-WNB*@7)QXmC`ju0W%q@77gAg0tQ1W14Yh=ODY zOVtd7fe@r83arMn&GZ>I(jYlRL5iqs$XHa)z=}13BrR%CP~~Tf^FMGJlLBZ$?KB33 zDA)@C8l`lqat4v9%UN=h6HCjlKKC5ZGls|o;h_=MXf+r>2P`Vj{1J*2H3aC66z*dp zk^`|L_t8)2dR1|(RnF*XF)5H*90GNp5L)3RshlzGlJVv=A zJjDD`K754!qrm%sA2WkIfYzf;YOkC5{CxJo6K3S$M$-<{+zt$lw+bAN2Atir2?r8K zwkr(8dL9;_&`I`S)u;J-uQ1spYfq}h{W>GzIpVe!%hI9QL1NVUkZT zx!ZT?`gif#E!8inEj0zm*X_}=I`mkT?)-78K4|?=i|cMtu#jG_VcNOm_>Z_#i*{fm(8+y{8anLHJ!BYpW5(_J*4UG#>V!$wEG?2SWL{a^dF`5J$dx541enA ze%IW;0D(Y$zq|KyCi|CnN()^D$B(N=cV-(185i#s_GlaT?#kuwNqrNZ{v6xmK7PMC z-+|#P`A(Z%KQWiDrSysDa(L45`t44?Sv3}PJL9ut?$(Rvp#78X%6B^d|EV6m0?nUs zx8J90zebZ=;Qy%VPB?TOo??1vf11aWt?K1txb(2p23PBP{2GKmJxIS>$bV#KpVO;< z9zJG)?!&ZB>pp%khNrt`NoRG{H@Vsvn*b= z2Z)*C$fUEPe}a10oBUUG^4H?*OSJy?%+~M7!GDm}U(myU2VeX;?Em|GblHY4!6z9q zHH8?Y!aVA_AFrEIc_MVK;W!|3m6cR&y}aHEuiN^I`urN`fqT1v*4G+Xx`1ws{1ajO5XT-D*k`n{4+HBbN2FEH2JUG{1#Mys5>}U`+=VmLDAkG^WNq0 zem3IX`_z#%QpS2Bd*>ZJiBF%`{^#S(_s#Iv%4P!bdVR^qWc+Ft!s*(2L&K6ZXUWkr zdf*sC3%PQ52c33qVYrJ>w-S_X`4edaoV1vXcBtnYBNH!rV#Atl-v@t4n zhdw9H(XH#%tnNf&N^N7g%3?VvHp;SU=d=3aJh?NlrRS-~l;yp5A=DwX=XJVh7#NAu zbTr%=Wd+oQF5g);-&;zCIOZ_V%8SgG!sBS$^W*+}y`=ukLvkZAQ`20y=7ddITDt+V zkqFu*b=}!kBY0YFg&6cCP49#QYC9kN{`ED;dVP^wjzs!<7*xl`9DX(UusQMKAf>SB-35FsNO zjv{@E0!oMgNf8{{G1?3ffGui42%s%s1Rw&7W{5m?ntP5(`v`p>y3Tm%iiB;`Hmwk% zWrH9QApnRHq8L;QVj#IBnTQo71r{};tUy|fAZ}>TU;;2Q08_>~d5?G6#3{oyOOUcFnA_5SSq@)(a0Z2j-a)Oz1>|<<% zK@9_d0003b)Bq}~B*5|q)U9VrfkY4(15i@v02>NmSPY$koP*?~6I>Hu6zKTXTQ5LT zP(@;63aev{dD0xnqcV_FQEUa3KrE1g8sq{16%z`uVe|+T6H>wnaz>GswLP`n$jqiJ zX;h>Of;C#o*cFx)#Ut$`Dx*eOX>f!53QZ^kTh{HKDW-YTH;>w70J9I-;oT?S{NT>b z(Z-dX+l@8HK5U!v5^frkAwdK&0NR;$#mjE-o}M|My;_bBPgdW#e{nO*UL6jOXY(J< zPp|BhpWNBG3(fD*d2Z;E@ee+X|LwQh8!mhO)?{PHjfdn7?N5=etJ#qD9Og%O5vy~z zTqT#)!)Dh0s99baxL3+S-ROtwSa|#9cKL%l>EHiabDiyttGU}WFvO-?F6I~289ZJ1 z0k@Yn`!d=e&iE5s8!v55r8b3Fyjtd0JWYhQw3ztm=6DHre`WQ}7jEyovHudcu;by* z#q7VoIJvxA{Go%*^CSGqPtsMRzj#XzkLg=qvlgoSrP+3tXRD0^Hdi;GnWm7%!~6Wf zPi>5q*_oTE2Q%hjJ=&Y4Q+x8gGTG|pXXg>zcfVa;Mtb87%wEv#9X)>3e&g*Xko(xB z@_1JL$~za=Qu(Fr?Tbtv*D`V`Xub^g0!$gS`Nc_*gyTieB-Nr1K`u2 zO-sPA+8YlkTXKlQO-)hrdZJxud;ldT;+N@WD3o??7w239n+s`Qr3GzyIwl%i8@n z*KJ|Gb1&OT?$aMH&tGVNoGlV%d*cZV+J`XP?id{=@i*-X47?8AL6MmOYb)g<{5fpK#asbEc?C>;93&uX1)oHrG+mh zm&%*BU%$S2^I*6+VfLC@&<-VHB1|347IgY>d2;W3@#Ny<@S+ZD;cj-aTs}Cb*}6Vn zKAqox;2$K9Ipx+eGXszSF(VRM1J0p0zA(Ap7>*{x+@iNoFRPPz1x~a`#$?RpcvMcx zqL+HjRboX;IP})}f%D_iMju-n+P0~>jtpBe#dx$i86&8s)I3<894=|Zd2R}qO~$z! zMq|3e`ut9J-jT2Ka&yz{Z1T!9v)H=cjymW26x+~NZPWExn@{?o&(OO_gTTXhZ}s?Y z_fg|wV|+0*m%Zbep>xC>U<#@dlWG@36@d|%X$4pzqz<{EAyL5!0E$*LQ-OqnggH>5 zVkLTkL6j_*NZc_;p`g-H5(Z*mM)u@00AYz@LUcWPG?vr=8dN6EjomP|BzhL#$&oUG zXSp_`#0u0PRdJ2sf)zk4Blg@m>lC2-6kTaL{&^cp|84j(E(=21BgjA0zESVDzbu6B9#@X!w3W}s`!RL^p zQFJNAs7*{!V*>1w_DO4yg<=p!aH>f(u9~>0NEyk2KvG1D#-gZ_h>C)!AR-euB#)Sw zdIJcCl}F^Z*k+c=Y6L*Vg1jRFK}{n3%8i#nR8hf{3L8Rs3@kANbifWULo1BIf(7G#RXDUx;SeklwvVg4AGB3!MSa_nLkx~)VDkLN4 zo>KFUVd6;^~7!6Yp(pX8xQlNO+TK=-onHk^UiLJRE=$WX7Xr;SAN&r_=k4ywky79 zHik+@X)5lj;D)Xl%~AL8QPo?}vHwwR;X+?%;Xs`$`81c^b;7wGZS#{`y!|zJ^>6UC zt0sMh%3)};4(C;M*w{s0)T6!4Z0FYG)3f}_eY3S?`i-6?)Lz;vuDs$WTMP%zZF0WD zudPSVJ-xQv-J!EHS3||E;Xr*2CB8TLoBO94>M7u(@#+F8!jty@S-j z5UQv7?A{0$@#}!=jhTmVD$gg)n_sMd48#-D{oUy6bH~3 z^;TchH@&tK@a6pA*#7j$Bpe{*Qc&cU7d!Gnt@+0wgxBrU5Ll`|}%lhPG zesb?*_Tfo?czSkxGUwIdu$nzsW$#z^xUL_r*8bw+T6IA#S6>p5l2IZhOJoSW1i7gsLVBp+9W)Tu#Kt>>9VkDntIUZ+fT~_*p#*zbcOk{G@0#KvI zfa*{(CS(I{sA)r`6nDr{-Ac38jJIO@f|GH0Zp| z9RcbyT%&>#ph5erkFAVgRqo|B+BFWVwrgnkLM zFhm90%qSo#SQRfSBBCfFNtFQ%P^OS8_et6WoYV<#tGTQNNem*8#0WYPwxl2lRQQ$K zH$ekn0!@ix>??^YMFO-4f=(!|oLPD9fqQ{YT>@1y$ZDLmo(rK-QUUHT)Yx4>P*&rx znVt3bz)dV&9#T2YhdyFafpn%2F9?DfNyJ17u39;>EjMn$y(i88_=DB{!n`U)Ski+F z*_P3*?frNu??-@W96CTQ3@oX+50y7^!{Oc7|I^ftOT9H@Z0$!g-VEK#<8U^0-zK|1 z<RmI^apqP4RxO`%0rOf?0QnY>hWyJ-+aKATK?Iqw!KY@66(HNxAn4Vj#B6EjqQ)N zMtjq1ukHB}A6%9m`j74{*6Y+o^ix0F&NNtCXPUSt#?9-${L;stY&CzY`e52-xSq20 zzj%Gro!h(L)x!6$zTO?9{ozL*(<*=IeE58xxs3_iE8Ea-V^-qhQ+j%r)8WN=b!bNA zAlreWr`;8uWcq_il}_qg9}O-KMK2B1zWM_A*QFSyM6_nZ&u-`LW43G?=qh-M^C#}4y_?OyI~h>C^;)~?`R#A| zEpC4P7nbkrrN8_{_nPb%Z;u8q@;sMfjf0MACzPf6h2DEanX5*x%ypWbKOT8bw_fj` zHuRfcv!e!HcthkW|D-be$gf>xJOJ93RkOT%e9?82OL)EE)zS25B3JO~^S5sdKl2NI z+>K7YJK!8&`~sX6^pD=tn`poAQf_Y&jAd2p<4P74j%t3=tp3jW{7N?Z6E9po$-3Wt zd^&CO=V9|=aPhmthuPV9_u*ATp7wQXEFcOqY|rQdTDMh&xnI6FLhShV(r~_hcj^qh z_-c4^R{#CqTy1Ioi_dLbG}U*H&bO=4e%b@$$Uu;`YOB+a^za>0s(E`E5B$iS=Fynb{1@SB>q=Qb2eUo1U&G9v~eRw70qA_wRpfmg`N;V>VLOeK1e*6VhzJ(cVVV`4Gx z3+w$Pb1hVJtm2&D)Vk969g_`;VO~hrr1iS(+O_tUoWm?H^UW-aVbz4iy3r-s!N?7A zXRRBdX=Sy7d0nS&Z6^I-yBib{ikN1L{(Kp8!3o1^8J4F#jK}usZjp>>B~3xM;nC{; z(Svtr9UXecNT$PrwQ$s<4}=4hNDWd>Sh8^>0*nZTjdeuEDH&m_2AS9zB~R=% zQ_3X~Dj~5kWLXUZs|{*)7_7PJL2Ou69*Ml=~f z0<|gy&;YDOfP@;QBWy`j07XVS4ebDWhT0jTQ@C-6~N52 z35e8U2$+%y!H2j4I8Eytw0bQY{=i=UjjTuols><(nk@l#J4ZyN95S_D7 zwX(XfuJ@&+FpUiioE??>7vP`tJRx&C_Y`zBFK_XaPZf*1as2h*CLXU1^9RM|ySBWo z;iTY`ZM@XAan(F>#*DMU z1HS$PDz5;43Ajj$MXI_wH0wUuT^_j~LHtHFcnwBha+fbb|FSRdb@8`W@5OFrs&bon zk8Pu7iH7>Q@85o@J^%UTm5Q&uvt!m1n7gOfD1DZ9zE_*Sv#4Lg^M4@=A4T&UReBM2 z|8y~R`RIu)wo~4&${6^;`A@o=f40S{@?um6L;y$ZhqTr{YiKs*WwgL zJtTqCtbbf~#Ust$LwJSUpUT`5z+Z2w*J1uIx#DYK_!nz=1Mz?FZdSDYJsezB^Et&W zSd4ITgz-cBW9Uxv^XH~8y0$%eI@$a_?fybG{MUvrd>8nywNF2v&i{{e>pi~xH_hb# z&(fcLYno(vnb;o8>>a=9%zoVc_!QwD;W0-JkwsQ!R%Rtl169Q!x)Dh9g``F-EtXhX zOGvPEp$kc^MWgNlgc`bmZs?+cf`TIHtgIo)EF$MAG9ohO!Dsj5XZ+^xoy=@+U7Y$K zY`tdt-M`=SJdN3(#?e6MqO`)<9N(%|?^NR(OW2*2&5dOKAK`_Eu<>frVEZUL+3f@N zN6Y)YHrp0^A|H_{{C-Xl`yWha# z|HBWj%i%Art{lMj`(-Z{hMk&fW#_hz)!dxC+lHSm+@`^sJ@-@O-)j6b(ES;D;w|X? zW_t8;_wY}jZU!12uV-nGO2a*JeKIaa+ZDbIb(~44pe+bw`3d$um;shy;NQR!gq@65X83 zHZSU;@Xf+6o0*Rbjm(m;95X(2?Oxh{mWOrQ&D*f~;O_4G5ATiR>ygpWfQ$i5m~z7z zQHUBnIS@tk7?mZ{H~@5K;x+;%u!sgpkpfyUf>td+qQDYO)Xc{`L{vaDNC<4eA|+@M z3_`Tvi4cKIIFa};cu-A2eT<#QD#8*V1L`9W2sglbi7UmR5>+$Q0-zRM09v91Nf08j zfh31wP2)(Y31Jb*M+h1MXba#~BPcR5kVX`-fuuF@K;f~*l4u>WDs*#Q5dwjkXJ$44 za7c(&r2x$l2WWdlmI%o*=U^mS@hV=V5vZfd0Q2GN4D0%`)Nz}X4xC@Rs*UB zU{D5)&`@YYiwG>t>}p$7JmjPsO0pI^j9n)Yd<~tpG59WW zx>)w}Dt&Ifuvp|V=tOAvJt|v|H!n|nXvZh&zg@b`*t<$x;*D#&5wxZC&qNM{@%$XOONjlQC56ya`9qzDPPaorUfk0Fb!SP(fPoW$@0C` zB;P8Y-P>Bw>dxXYX|rOwzHCl!ZQkG78eP#VnN5cqG8}1=%E3avxdKYUvuj;@hkyFp zc4+aBeaRkx|MhRLHrw>mPmUV0-#&tysr^G2i_X~);nd9TB`0lDW~W;zv|)Dw^JMkC zUTUisK0mz!$*+BDuDGqc`Jnx+-)S~2o`0HClh~792sNBL z?Iy{CS9=Dltb z-|4y4dZcFO+36|J*MH#l#C+;yn?H-?hAt2E;Vqa%?3et(&&Gf4>jy7r@ukh}#^W0k zzuqN3x7&Y^pZu%p7N(=2y)sH{*p8zO95_V9*78!?T2|F0nS5LZ@qBqzP)d&m#9{6F zs_O7(KeS23Pu+CW^Q(Wi7+-3Nr$*b!x-)Cw&HQdSgkAI>2K6`hDYm{7Ef4X!1lb_5 zTj}|y*7K|9jN5>?A!21oqJiq#G;^d0&QHek$-%?PY=5y_9#2gb z?()P{aenHjSRn_ZO=s8AET%o5XfuytHaU^*E@*2BZJMT|BJo^Tw(-l@P9h>xVweyS z8>}Z-LtvxErNtmGA}^;hYgAiiN?B7j!R3(V+g$c_b<+BAyASWb=YJxH(NB7&AX@-3 zle1is>4Q#uJm{h&N?E`}w89V=AfR-}N&y|*1BGBT#5hDZTI)TL^Raiua-U;wAl0|H}) zoPjE#AVx?c2ualFXEDqof~E#TqkaKmKr zZhea#f`=AQ}>*je(Vc)Nwyi6h#$Qu+b;lsy0grHDZSv01W^uC?FsN0yfk$oEu7$ zG%ccF>!T{F64pp70u`j_+u*AXRfPxu6cH6r0fRtPAOKdi8dFvEpq&^3V)P+20X(3h zc!{lumCg!RYCMAFn|mWvLzogdFbP@1?8s(abQOq5pf~|KrOr~!P;87zi~y{}2ml&` zgbo6*B%}_~T>EQXHE36;0!Z8`E@dmX=duy&#PVt#anq6qU4Q{pF~J**TrZIyhPX!V zMVDqE5qdkTaCOQ(ZLEQ6HJQz71%hlk$7G0 zn3oBdvVZH&VECi-=^l)pGHcHD8gr$mbYA@?_yN+gKA6o%1Sw`8VN#=c{;qC+i1Qsg zf6D9}Rpq<0Y7gqqx}7&cz5?TGc=6}$?lLVNn}TE8kHt3lbu1T){n^-Ooc8*2n?-BZ zhp>Gq38|h;R;8+OX138xpM}kXy7&4laSqvJX%a^{N8*-><1V|~ET2gDOZd_nY~|V3 z@pAs|QML#57yAeIE&N_Aufc^slI-Rg)pl6a2b+so(B;J43jR=6=T^}7*~5!Exn6H9 zVDK=nr|sn6==>)1Umhje?;R%D2BcSTC&dZS#!9L>zP|>u3-sh~k!!0g;N4EZ+W>35Bm)*`W%G*$%1O1e>GPhfOE zS?;#?er|EKhVnuGzDtELTyWj>+_+YW0I-x(wwn78f5;{JyTP zgZ@!F_#wdG?-n=X^goMFF3r`S6l-fmcGHeV;D*o-pi8^(VeV&ZCsd64@DSvUMRFba zm&)Rpiyw#S%k{&*u-vV2^UYDW7^N{;w^%x}WwEt_epPjgMd%hW@O#PN$HQ{CapCIv zGf94p-P7kpugG8nl6CA6s!*to~!9NSs(M z4ddC=R8?9PSkovb1OfRHta2^vBGWCUdu7d7pyh9DA5r?w*i^@I^Yi4CYza6k?jQO>+k z5fEcwj{qvDL~IBMga82rNISq9z*tHgo+%$lfP+HBGggKd7L}mt1wasjm)Iek!6+~# zjxqY!v8Yw9m}kydFgdUg1cVA{iP{I;P=lREA8hB-wh@pOc8zcV7Z3!A&$v|v(IF{h zLOH4fNL5m`K4Pa)krZ)daA6TOTJbgEk_{O!jA)n*siKOB5n=#FFw7}v4x&WS0B3|5 zHApJ}A!USCpcZgw9Fa!^G?t&ebc5J3ArnvvM4qUF&{eUT$3TjSX^Dh6C9t42s*NPX zLa0Eh;z3&kI8#xoo*c?wgBbrZ}*~k8L-Q*W|eHPkxWU^i4PnR33w)$v!Jo3ekZZ4ty zuAdB2{rsl6lh1z#j@Q%v&y^RGJL%!Ou(4~dJdw0yq5`pwP*0Y$JfVZ}{2SG2Kg&MX zTboSHx9-EH;ukh~nVUCTb~xW&KWVz;=xzx$(5_y)VsGXd24hpG^LUzuagJbV|9Jf4 z!^3sgyPTafjX7P#QJH*tr}wB{ef8+@($KuHlRxa>!91-OGj>Nc@Z^ASRJaTMBvLOJ z^^NsaH^+}Z^aGQ9>B`!}^yquNyR_LqpPr{#d>E?B+sV_}pzG`k;rMiM@7^TLT-ue( z{puSZPq!%j^3Lc)>hDa({igVIviXRPelL7<-ic2~Y5V?s zIt=Og#VDEuK7BBW#oOQ6ES10T^Ro7$)+OXE6do&$DTncW`rezi^zy`YSoGZccM^xN zb2(PV$-G!p(}(L1d(V_K*j__>bqCT-Eu7X1{9sITs5kTF$8U$f_qFh(7Qg(;R&&|E z50g>Io@~~S;qcwHkNaugEiVj=d;U2tKdp8rboc1(x7agWxeO2QRR8FA=1(TY|N0A? zC)&LF+Q}$SK5@NwmlpqWe%g!uFRh=S=k2%S@kN)cQ4f|FmUyySy}6jdELmIi$KCP! z?(V=9`D{x`hgUik+uhC=jpvKiYAt?$8n?ChYEmnZ33;7{K6a z?|i$tJU#!|_R4I&!zNzXs~%U2um5a1tVXYFUgkV$lUVj_S)>i=O8gQRD?5#|aC8(+ zI@s9TIrqZ)##3vTuehh4glb1li)an?fHjZ-UO`xx=BPQnyF56mX7y@yQcvd1xRq|| zRQh%8YHEX7g;mu}ySD1WJVthG5^FIxR1hm*L?E)n8KEOc5K=;)xi%l}4z@3jfZB$u z)oBR(kxJuwHZ{F|+RsZ&db()p(`AE9HYJ;81I%rt9Q9;5`FJ{!Dk`YiN%Ng7&$2bj zywuBBwSj8Glp0IMC72bsm{g|^Dhe*MX<@OK$_K}@*Y3}HkoA*ZPwiSX4Uy(pua3tL zAKpHF^g(kEjWAdDkaSMujTsP^0EmPj1Wcr4z#5x#UNAEdgJuXr zqCTKgFlqs%gNT4VgaW}bX3ROV0Yp#SAgU9BPSpcQ#1LeKS~DpdGE^pRlnby5f;E;c z7$u`hlo?bRYWF?LZY`6fY7Die+#Beb59H6jX@-1{iw?BI1Kag$OD_ z8byT&3<4=a5RB5GpJRta03daU0n`#Wql|8aB#jI}&bZVKH6IG_S%V90f|Lt#^j#N1kgknQb5`y; z%S5B10a8XLWOjxVK~ai=0H9(7jKrwL*=b?I+|qgish$aWl`Q7WDN$-|=0GF*09D|5 z5M(tbHh>E-ow1Ox%T1nJ08mi|AOI9%p@KPMjHrTv_~x}2!JukT1tHa}qu7~PVOW@m zMP>^t03^l&5`YCvfCNDlM1ufystqC=w3ZpXQy72@Q4fsV;b;&tJHKb+o6YfNGJ3jq zJ{oSUnJLW~JCqi^(U<{(vVsC}C&Q!-!%iCsv*aVO70JlMhNi@P%-y=^c}S>Cm!&zk zY}Uu#D1FHBhF@ac+}_NlPxMmcVQo*#{&1Mxd=IjpLiz-R=b?k{IMykE7xVFnKYiGo zuQgS7rkytOn!9=NJ-2oawm*m4Im9`nNvzgGm%=>Z zcWiU0i%SQv`MC7C9rygT6%6NgzJrHP!0w^nyrs!@dFygEU10yj*3QF@o*rQRX4PFV z_9&NLb+ynPccGSt3 zsv94})|=VEjvhZ>Ul_yiPIf!NyW8!R09WhY8m9gfOk9Efb_+@H|P!L$!JIO=QXvksBm-K9v@WiCp|n|K-*89Mb#6<8`yTAx}rRHcc;3?!?NnS*}+30(6q@SIxcdbko6T zG_*5Ex7zj!G@nmKx4Hc$J-R6IOLTskY`vDsj_bX|yP!G)o4`?t51M+>E(R-Dn-y)3 z{kf(*4}7`wa|rLw+r6;(jLCk6gMZDl%}hRJ&pXVGDGP0~z_M6$^Og0GaqlRR`)Pe? zc<$4~C!cZ`uJGk2G(4qLD5(li43r^E3`8?ONX7@tgOll_TdT=rIXh~Wwr!1zGKlP! zAue#$t>(Vz+BS4eR6b))IGYh!gDG~X0j;4lVaI5QQlpt^yJFas`Gu^wlnl-I)SgTp zcX(I0XLMjSEeE|~*pt|guv}H+y2}U$95G2cN2t+=n=F?nRRwGdhV3HVE{L+kr~Nn| z$C(%iB?H+kx}?A`PzmsLNv7tR1sDyg1*#v&sTfn&~$Dby62vPJ?O0&W2( zD2hP=0w4n9h$T>ln1YsI7*LQI)FKK)$LK)=XpGtgs)-tA0(4+U5I0rj%(kO|3MK>_ zyis+E1;LsD7Vv_Kprke60##WJszj9t)_`-=6WNReQ92<4O^|wqiUc}^CPD;=pemwK zD54D+5JU?RKrkQ@5P)Gp7ZlFki3usFh$yRA392HXVlX(ZNLQdUO*4LcZ%_5C*Uy4!MMM=;JV?2tgqxin%5%K1c{bBxs{rBNotbQc%|S|oJH38jij#9U$#$$M!7 z&NWOx49Aqah445!AaZ1L;GXoXEO>TDARr}?0l z$2DHsL6<<}#5rIj0ATF^*Z`&=QM#(D@{W35#a-3GqL!ab>hqMnSPlA_0wUXSVyaa`*OeJ%vwRgIa0vh$=V7b0_2q*KPLEb?dNp4zKpnM_7qg z96Aj1CFgT@^x^WK{b+p6l>hOy?T76D)nayy>}SjTA;N!HKoa%pI?uA~hpXht#y-!f zG17B9Kd#?CIpGlngIsf>iF%jCQk$cNza^uhj>8VhpI_UlkNBH+y32)m{&Lo&M+lfsd2 z?u@IY@}mzk1s+b-OoKX=!hr>9^MaX7vaBLbsdeI9xb-pp)9>0f zN1uHGLP3j}Ve5CV)w7OXf6eC6uU%-AGw;{e(!`Xyo$DsUcpu-sOGi*?f7ZO0Jp5XJ z$J#&kIeYBktKXJ2cAtEq_~60AU;WN8`60xA`km@Zmwx#ZgNHX(|I8gZUtD*as`AFNI@hJoCPTAhi;GaNR_`9p zLZ0q!3?GkAf9>wQt2%r(xopzbub*~vckp(PkanMjTIlv&yXn#=u4e1!SxSXgyB1CZ z)=R3x;;tKKdHLMhcAYlIdSYTZ?6$=&sBMvysl7JL~0XoNrJAg)CQbKLWefPvr>mKWX!;p z>%Fo(m-I$A|Q&5cu6iUSglaXG8z^OhZ_ zr7>9c+cHnnY(1HEvyW$!4k=iZ)^4y$DXIjm+HR8gyk~~YwOK99%q_6=84o$+P>VJ} zyXDcz>BC#q<6HJ%E$6MI$7$x0j9W}>l2ox~r+0|%}43KyX9nb<`f(l56kr)iA2W>P$h~&W=+gbGh4Fpmp zft7Hlpb99`#5j*A00bz;sf|G(MgU@D24o^8RE0Q?@iZ_xa+zur384`QB8W)LnwTz& zZNv~zfCQu#nMZ*bTmWxPmvF>PQ3GnrNQg!t0xK#JF=G^oA|yy60w~DAahItmbJPOO zh+=8f5`tw(1O+Il5PHU?O)KXo7*8M)x{M_;m~%Erd%a9hQ6uEe_EJDq6>TESeB+}> z1tnFZq9OvFs3`V;`v9n{D%h!MHA81fAT|^O`hXD(kU>CD1Yd`C7GkXs0iuFH6j6!N z256!xs$ojlBuq-Abf!cU)J~#O0PH|j6;&jtNE^Z;XeX757g>TRqeGM>ab-XqVhR{Z zebCrSfI4U!RaFdP#$?H81PW}4&)9Jq8?UV*2w+rDL}frCz}TUB1mUOluAgzR5F?lf zK7+2OIZ6%Mcz{Yhd9hKAk_8);I)E5O;vay}5ReojNDFc-x(^Wn4N!%t;JQU1$Oi!D zsEcfl8|V@X1CtO9j0Hlq1_4Dmjcpm5D)@;%>0ua|6LpBmg|Tk#qbW_ZP1$@oJYK{i znXhN2u8+s_{WYrga(V=p-V57hmT&gg@0aU8bGtp&Jdf%YB71+9 zo^!`fuXnTXbE%Ikwj}B)=}&*GQU6 zb4ON!5Y>G!4=0oQxgWu`uV>BGZ2Bd8bs?7?5|sMGt!8Hl#lAgV2XlijtnA)>zQhoF zklJREhV-PvlW=4p)OD^-)67I6iNmg&3g3!wcsRblmhh7spScTH-lp}-)O-qRsU9xp zP=TG~9~5xsT=VokTzNNdpXU8v4BM+US$D#sM3W52o^ z-%RCr;5X*5(`E}mKdm=z0Q|Dsyw&QzSx#?Y@hj;ICvNM93+Z~L?=MqS>{;Epj0qi{qTfdZCnr0i{v1O`$R~IgP;Ns4Aar-y% z!3#M3v$Qt@`z}nMfcPcuzlQFAi0uV{&)Riw&dq73r^AtMGn_`u?)mDY=8YaS7n1U8 z*!yp~=JQbhRle{Y+5DR_eL>#(^WmlM!4vxRDz*Z(H)&(r*`(T(ru-ru133Ay{1nIafQ|{b7*!&jdSE2m8K3S>++a6x$KI%wS%M-jV738d{iCT z1VgVsyPt%2O*Ja6KH0x^J-c+nJhP!XQcSc%1&?({&_UJc@@RhY;H18LeCOlSkH0ss zUacm+(X7g9fR0_Le4Jvl95+pD{nY!aj=EA>u`EqcTcYdrLufLC004jhNklmfeJsrri`^)DFl1 zur=Nof+UyLg({p@-J|^U^wFc`ZR>}dI7*xvHi@bcsG}?u5tXejEzAvB!j#wp1XYQ8 z2BB27kvVW8zzQ%CIaDFm%tVz=Vq^q|7?fMEfQ(?z!ZsanCp3wA62JoFNXB4dwE9ARz-;oeP{qV$eiEHAvJLA}9hevO{EM2Mm#`6G{)*Ig15? z1P!qZL7)=?GMISrs~AF~+JP9AMCYJm1xu8Zb}6^1RfwqEq0}6#Vj`fZKtRT^vr$5f zku|ag5f({1>6#eXOCm5rJx6L$1y$7hC|C(q=m8qi8hi`>B*I|~xzY$ASJPM5i%s5= zG+pr0QyfXIKE&u}+8siL$|)EHh(Z!EMpRV;)JGkV0*D|Q4Gua>f{>C%mWUq6gF#_J zv#DoR7G_`(-tK{076o+PQa;R6bM3&7@|s06aWQ4 z5JmAugHz$B&R+!#2!U(^y%o%?o~F`9X%w;yih+bA3JK)EB@jZY&_M(Up+=bzsIpOD zRi~N*kJ$8p7%7rf>@1*xh-}n=G##Ggra$0qh$i=rD@$!6Dd0eZ4V8NAyu{XeV^5ty z2mK^Z>FS6ZrNt?Dx45u7Up>qYzgNz6{?XQH?(=NCo~*(!ZL^}k2^(l!Izl~5_;E;ToJl!`ESoNS7UIQA5-z&q)TS`A`H2i{UyC~rCck-n@bp^oss2`> zHcNp@+010N(t4tgCe`mw7Nv2YD2x3^^}qOVemTp2`N`2Vx8K=^zQZp(VSjMD`76IS z`$T46xS1yjPrcn@-Wx(U1S+69_S0KBqp*2Vy1M?+?Qud`mi91-ZWvl(&!tbt{{cSU z8m`ybrCmoaUoyTAp^isCUVQu2iB0pDpIEpkY;6GS!rDkb{?PxAKk~Nz&|HS&di8?GO5+yxaTCl7R1hJ1Ib3`ds`mgunhB|C08;{D*eq zbNTV6Y!xz0bTy}gQ=WHAPt)Vq-CJMD*8_a+(=ccJ$&YEo^tmsXyBp0velX7@e`aev zCv&fj=Ph3vIG@ug<1#=sM@#mp-Sn|c{>lEM%Q*Pb?p`%$?!Ymm#ZJ1p(xchnRzu0X z+k+D6xtk`vq*WPNu|@0}gbJre$G>;~foA1kv|~bZ(oV`nex+I0yz*-&0Sj7fdsubr zhshwFw}VWQy)5x2G|)VhqaW&i;rpA*b5e6_@WwO!8!xSGZJI#=ObQ@P2g?S0pc$Ch zYE{Pv`zH_YJ-+KtkKA-Qt#skL&1yDWKx^E}`Ng!I9-l(Y$}Q8Fi~l4O#mG2O@$L=VtSmd#1m zngN-Ti)gdpgaj#8tJP^$g`6ZagQUON8)O@r_rphb$KQSbbY$InkrkS5nsntR`+PWI zSs)u#AO*A>GH!rEY$;agSF-;|Zy&Fgvt=j@_BnwS#|0opazPdv=cs52Q%438f(*eT zDi~BCIU_qpQ8sK7LnR~w4NfHm7_sTwU}8%iETBUOU_62W>rgYxMFJ`m1tchRn4>1l z)}~3K6U8F|K+nWV%pi%xTzv*?00jvMoVA&YA!-PcsydAjMT18bMqp0ZSSDi(IPRq; zHEdG@BjiFg6`Lt&=VOZipd=a;eAG6?K$svGYE(0!9+{L$Qmd&NjaGwYN|`bN7fA_} zj3&dFHX(K~s8LN6TahXTiJk(IC;|~780|0yj1W8W0F}xLnXEP9KypA6IZ-zPS0Iv- zN@dzH*P#x6CF(>H@lgzd7l?pt)F|j&;Z#5)*%%v@3KdC#pv4#o1XNH6Y8C|rQCNAH zc}0@MU`*4H8H}1lbf^Rf$^dFq5`|^NiGU$W3`PT;fetWzS@OTdK@a_CY7Gyn=p z2qAc`U9Ar}Tgocf zfyPK0@l@q*z`je*XT=D0mz(i?@zK%2F2?5u-S=($^)TAP!SmVaI4ke*#Wb38*lG$( zmyMd{(h5e9rA7|AJNGIWK)34-g0Jd1moVHe&B`@*1unth7pzO#>1@82gKQRe)IA!* z?lIh0rh{IYW^OBydMF{&rNc*)?$J?wd4ky_{jhYW8}!_VaN%`ZKbg$GWX|u`?puq8 z*I@ERyYYMU%HOju-AwHt<4v!_v2G8gz9_Roj+|`Vg5~S>or?ftK%Bo+ykf84ZsEJj z#~DjM+JWE>7i@ErJqf;OJ!LyPG`fBn($APs%fa2a`Kr3FnKz%XCtr}4-_&Qm?!py* z^o4MF>Fr1IsGr2k>5Fffr+&w*Zy~)5rPZULatPHvzFk1O4bObj?*5}>dJQLk#6LZT z;oWrF`Mb;M?m6haQl4+Jb06BlMhs8K=|~?Wc>XoX|AW5&3XT7ae)0p@`CWVPls^2U z&2y)){(dYi{)tFaB5N${Tp@pWtFQ zJpTD`{v<3v@Y5>*f7qVyc=Q3LJHj``4z=!~e6T$J;dp)pW}nT%2hjgUmwX2JFS<+L z=HkDQ>F40yU+4Tg^y2^9-26Gx|FdpvYCpiPgN7iUE=~@QX9QJNuD&AuzgvY*L;Ndl z{l~ig|BbWH>z)4_UU>k+uiC>cS3FOjZeepqy91PSs9eqC?0DX+4w~LB$S3XIK5W01 z$gVm0M7s@;P4fq7H%`XG0mzk6bEjW?-BvfT|EKYKp|-&xLLit|_5Nz{)C0^u>V3F| zad-J-3vN}!Ci=Kc)}Y%h{7SpSrLKW_vfS$W<)ge!88*nk;!&8OYJmkTi!kQGmMB0X=<^(VraMk?b^5oXN{r!(0edHFaK{Ht_p<42=(K1w3gn4Tx z%hgFEX_ybgqzPsfIh+NMjJ4KDTDG}W1h1eH6BT>r7e_>bjI`un@`MYtAV%r9mc!c4 zF)p#1IQux&EFElR2{co!s;(AosiZWcez4M6r5HGJRV|yU%94JT=e=Hkv+Vc%G;Qba zFZhirDRQ@#lVQpsxgqF<8@Z5=9{jj_0}$FrT2`UvN6-J~NQv1=|^` zMae+tq*FqqjCh!0=%ZLbP(cMF7C`}{c#Vxo2y;UN2x}Nq)f_?}Fc+jK2$~=zpaIk? zBvI0cC2AijMrefqh?x^ZY$zqo5RM`|RL1~Lfsqo#TFuSgJ)99o~`3#V4$$jf=`_*GUGs5jDo0!7=!O(=tKe}5i;RQbpe5Z9U2tS$cU)O9#jBG$tVbc zw_+WntT{kWIQ3X`X%W2@r&34gSka)Y1QsDC;4`RDgF!?DW}^zN=&9hLq6<<6tr(Y9 z!8u4wa0Cg6f(o;u5f;M8IJIz^P-3OP5Xc1>CSB-vL;!+k3MEPia+V3`p+Z8C6MMpj zFsdr3s78znhB=9gn0EvMiU=wQNJ>UsG}KzR0$pHbY?c})sE9%cOe!&o4;erSREaD= z9Ac(uy}BT!!~w*p*b0USod`i^w3!7xjq0N00zJ?Xst2uwfuA{l4XvP2)I>ucxBzQ_ zLrbv=OZ6w@DZpApCM06A787VC@zJ*$YE=|qjddFqO-v;A zpli!+p7hh?{8};3q82eELW1hiDdBO9_k*rt8(YXS!)4BVF0;#Uym0qrFwFQOO~QS% zIR9k!$;}ZkpXSmZ;pV`$t*uua%xX?EBzuFiHxH|ScCgqh-KAb{Ih(&dJ-pELo^H;q zDtQoB=MsBU^AD@}Z_3dY6feux{R#Z~VRN%gf8ol|ZMu*{V`PD`C0J+r`0e_?|L$rp z%RYB`aPolu?RVuonlC?{qgkDw-{Mc~CAaR!e}7xD zHU7h&Ob=@Lr?0kG)cv{V%R4>)8?#j+^2~@Nq;DVPm)LxEkNqCBrOYS#!4a$guMK!{ zh(CT4w@vbe7qT(*y{C=2S&fPO*&jDM4RS~fa@BDE7U;UfO z)s=g3x2(6!+iH3dKYs}hlkm^)c|HyB9YCGo z2kVLN`d3!iy3?Qa@+j9o(LE^mdvCizXRbeQ@=s{D8Ki{-s59UZrjwHoPaoEy%-fxW ztMb~3JMXG_IiUFbv*FGV|Hr$u5$w-BnVJoH(8-W-W5`gVNw7Me9o#>8xM<#L+I3^M zG;3z=BK7CmedA=j(dD1bHy)?sSIymWJlc44)h4EY%?~bv=4b+1riP(s;NwS&fBxZQ zH0XWt=D7(kzqS8pI4&;Cw=`YOwvM|=5kA_A-O`sw7|O-`T$$&WE*Kh;PvG$3__cSB zvSqOqw#1lO-=905?OyAb>qZe-u}IK^)GBSqO>3JO)JJ;%@yYaH|Iy<|C#MhV*n~!S zyr9)0+JFqwv|U)$Ra-5?0sAF*Ttu!Sv`npGXQ;IOBHtMnlBiHrRy9fqa)!!eh=d7% zofL%@WEHG7i+Z`LF-7hZr%9TmTNkp+=d(Ff2VvR9u0;=q43GqC#RiN4Wf`ketb3fV z#WLk`i_3P^w$o~QSXFhKu4j3fI|M_rKb!pQWYYJ^niSTgNuHA$7J_NChw+s5ALh4L z;un##K!%tKV=|kUoUJpJ*f5Zi8cGK21}r&Bf@pwQg(a|1imoK3b`F(s####qdX{ws zU=S9G5kZ*(B#mkZQ9&^(c&rF!1dXUFB}{#TY&bF^5~$Rmijpf@KtcxaP3#sS5iAU< zDuW`#s3EQ*)S?Igs6Ij$kQuCH>m*I1ssvGiPN40i3sFdvV<+OhG%YrfNtlFG1XU1- z$$&-TGx7~$^@tuAY*1CgrLwx37ufuG*U0LBWN=+ghq)a0(;FMS^&jPkpojS2x>^cDfLta z5t4vpjZIvZ6fUJaG5r)!NJD5v7m8!i;Euw&>I}2x!mAC?Jr)FCWDCK!4~ zHq3PH&C$e;xH+HA3*ug?YN)mVY}h3Ckv<@e6fLfdaZxFwFm%R39HjSoxLv1D85r%D zax?MP_VYGbtI9zPJzcH3lW9G|wo!SP%!#4o5N;mWyl?8h?oRRIB!ebhR=6GG25A8O zS<`I_Y$y3r3J7lF2(CV)?aL0${=fbM*4+=O)&FI^~{bqOkB2|CZ z?7au+Z}HsL{!)j>>fUSEfMijJXfAL+s9%?wge_!55bQO}G1ZyUkskd@-!a zQXkK!mtgh9!swV131Iy=}_h#jvI6i#q5*G#F1;kM^e%hi0d@{J>>jkG(5k z{)p@SEbzZvwV!~;f62i+T>STBo|FFnCSKSSsh|bSEO=<{$ZGop^{9NKg|?pUUW4Sz zy=-pWj{-jn^Z(TJ?u7C;>+ZVZpWvrDSi1}TA>f{15)UY>-`Cj(_U%S}XfHw*e9wOe zg54jK%Q=KAGciIPdl`LR6|1T7Z_hVOJVAGHz&ilZk?a2quDwm_aN^2m= zx|Ke|28AT+xqNs&(OpMjSv89WVldF5;lMs7R#QNYpoASeZ=%J9J*U>BJe^Gr$FsU2 zD_myIWhqt-&FG}U{Ron$l)23CfJtkR1s$Fo+&c*Qv{~!As5Zos*d$gnGf3^)I?^tb z4B(Lsxr~y5;en+Dz$&l`t1zfRDhLN$f-#atlUQb}41o!hSy*C>0aXD&n*dcn1!5K@ zBkX2|j+iu361fksK`aOjf|5p3Wk@Vq5CjEOAYA~Cfec~>XE~gnXd5t83ay@nXs841 z$XLs%p_I8G4H4Q1A{t{15kf?k%1Z5n)ILUJFeC=WofVS!tfIn<#KtmOYtL{KAOHba zg@F=~6gakB@QnzFVy$5X(ST1>AxI1$fJ|fvnV?df0}!D(i*^H?3HLydB9S5hw<5JF zs&~*X5KmDuPKL%Qao~iJ6H?2B%t{7`BY-NAR}R!y*}#}$1ZHa}BkK?uU0-ArMSTp> zFRVGqFfrN#bf^V@6%t@(HAWCwi6cW~BveL32&xL5CLJdT#GC{4fum{*WK^Bf0NoZ9 z1|_nhQ?OtV4I&@`vq{J$ilP)bQDB7}v=4x1-rfKpsvLtsLP!xDvZ0g!3DGk&Y=VL+ zot?CS!8J#SM2H3m00BFJ7=vIJh@XG%MZyTwX}}mEk_Q*j6E>Df*%DGFz@i99D0+58 z(Z)YuhIdO03q%D=01y#Elt!_RjF1>f5iI>d6Dv0<8*L{Gxo?J9(i^bIM<)G-KNdHG#VbZ_}Zl1%Jq|L^u4_OuaebsgZwjV>jk(rKtG`i zL&GB~7$&~nht(4OkmAX0^Zg$s+rXcD%1yV;$1Ub*_x!WX+?f0C*v3xH&7+{X+}p_6 z%2ROpMYysDjg^jck$QIB{U7u9{7Y-CC@hD}!oz;J*8vY&*n1{ONFS4gRP5y3y!oHptm@f0b_m zUmJnl)FPJ;?>GPCoyz6rrE6)mkKg+NoCEyw4SIAgzKWhBK7HOkF65ux?rviC$8WB~ z6V|WA&O}4;(VKkx_pLSEg`2fMw6Fbcy5;c~K7$XO|H@D6-ZJ^}UVm=&*N?hu;9lM= z(+!e=JZ`F2XLDqG#pRXnaJ#E9y#7{gGbW>Bd&U*`MHE6>@IEU%yCb`%3eGj&yo*c?YT(npMcH17%-*`w#3Bgw$l&q zul~`Si*4Kc;@-|;G5h!T9-Q;#PJ50pz|P8}89&OJSeF+UHsOQEHfP-3BQp>*vOf!7 zKk@?h>5cOK{r1jnet0x=FbA3CMJc-(I ztc0jD^A5)yqQcgiIXSHV{_jt(x8*Ng*_p%Q{n;awo?V=S(D3Gjv%IP|x5Qq^!EIpL z!_|KE@b-LJ#kGQ~b^N%DSNDqNt`66>czq3sHL(JzE(s`5Pt4*pGzaa6j~?85_|e0M zv)gwYXlDz%5YsJ8Cv7k_1PTrLrsu(zO`ha=p4p6CMsTo*cczcx z!TbG(fxN5?_-LqOun8qO+P=ELU`Sh!BnSqbB`l38t*W9|K~+)_2p(BUgt1P`uE$^; zs462E5VcXdDuiW#0IW#!027bMX<`YDk~NW?BC;Sv1wv)0h*|=}1E!rGq#r? zIF5;|G>%mXC{r+OtdZ1G<^+XQ)CX-_C5Q$QKvg>Rf&!>YAf1K|h_y!1%EQ7V5L-qf zOALZcNDj!TASeMc7)J0BTF;^^sHZBAK~W{eGoe$uX^IU?z!;1PSaz(2#Stp;S>Kig zG=fG@z(_LZ)z~H-k$@nw0maBM#x{Z|YnGTE6c7_Zff$4$a70K9E8d4@8FsOzi zK|s;i#n?j4IJW`-q;U?iz-UENH9%H4dd6rVI*1kpNf3n*jWJ2?5`-8uXjH8Q5P$$W zfB@hHy~e3&53LhIizG~uHN&irL0LSD3W5YgK?HI}QfSdASOrk<3@rnM4nxbr&p-7F zQiq`;51;}VF(|49%|Hpjpk>gAN~DHCkrix2@I)TF4yy&^a{v;x5m6fiKov$wOe{#P z5-M5IREZEg7-@jpy3Bn>ZIY$gU{jsdEOrPD&=AcYn*bC555UOQ;82$M$Wj$PigYLw3ACgnbk{?O`Yb`znt3PG9UEs$j z-80bqiM)NsFaG(eeFoA$nXm2@8{c-jH)QvhWoJ(o{TOL+fRkf~OP8t2FrlsRo4&EcQEk;$IWs8dZXO@ zj&A+qCVm=D|4K5tFU7y>mK$dDLV9^+M*B1vDPB>{^#HKF{07H|{jPo(F6mp1-} zR-b`Kf0fpL8jAm4_;}A$|B?CZFBznA;4p2jY(wu8S@tt@ z|5Kz+7;I0aC(*cTbTHlY<%i({}9~*cVei z_T6g>ya@H@iuzTwf2T1o!`A=azJ!oJ#I)478CMSOCV25p-28oauxIvPkyq})`PXyP znzn8R=RjXf!=q~Q!xMiIlFyZ!kL}ubG45&dhh*L0xS{f}?H*KbZ_wdW_Q~(!v%l`j ztDv8%c2w%fX4XyT&Bh!qAC{XwIR(GA0ozZcm&GNgyvf}nYajXXd-WsNMfHhW1M?(5 z{Y}2|cawM(9{;g=a|XTp<#f?aj*jy!NMGo0)!Ft#yIyGA0_=r_>&jdH=w1D=ua zW1ptWBz3cCIa?~SFcXsy;&MXO;xNzm2W6gcpOUogvSo7T@$L5>%=#;PN#hFPF)1-8 z&X8k6nWB%{p|oeHVFIzBDN;$$Lk=npASp0FP}_+S#E8;?Ml}%5yyp?3N(>Rg5@?2` zjL2q+dP*U&T(UzngjR`wENB~m1kVZ}8W9{&3Q4ZoSCxpJ5EB}MJ)jLp2*3laG|aRG z0uTiaAsP`7AQCVUokAK5?Yu^)gU-X*-dY2I0{a+!po1j6 zpBDzGREQd4Z2BS_DxS180Yxwh381JbctHt**(CONz+AD8 z8k0?^2{OxKorR4w+*|5*PBCBy?6Xy7rX@mKZqIROs%47v6UB!e#d$Hy3O5c`+V9f zivp-f0fw3ua{35={2q12U%2Z1A^!N^>w2JHc)=VG^qZ%$N%X}nvuf}MM|jbi7p^*f z5~MGoZmYUJt!-6h4i|Xw)Rc)l8l;In{fa+aqi@|Yh5DDD^YzgD)`7X4^0QCcWEY^2 z<7xQWp+~x%Pb?4_0Y@f9PqkxNsDGfp<5(G;)2?f7!aNECX08X*rB8 z3m<=szwtvJSofu8vqz`$55Cb{g1jD-r9PZr`wx^dm94h zHD+>hK(`*)m95TgFHS$Ozw_1figSPR3+2pIAHH$)c%SF9w1#H9QDwdIdVl*c_dmq( zWsJ?FbNU(X93!cpqULu}A{G|C(?i+FEZ ze|)&Oe=o}S)1uHPO9sH)L2q(JIo8``!rt4OVM(6!Poq+4MZwL)1*K=p7Z@*eERp*|^lQHECfn4`e&Y5EF{38bJuqAd)&0#R516 zMUDif*g90ic8JD}1Mf0Gj#KhWT+(PUty0gkOVTNc3B4AVm0_0YtmOucE zq!^Hb1PM)Sf+#@52qr*Id=DwcVXxAl5#GH5fCNE@Yl)X%VA=7(``yUI2!Jy+ztP=`f5 z!2X52fO~2O)l7>?+`&DDG zNV0S$-AQe>q4z?n%Xa_0={baI*uOK&!#%pT3!9&HrDWwjvvppxPw9G3PJ_-KHYXn} z%Oz{u`>18)$dvPeS%3%?g%BHhFF^4L$VxpWaFgZTgX!xY zoj1c0s$FsnJ+Q_7*uK7?3lKhCw)Y@>Z>86v_se;2E7|HeE8}cC9A_{kmLFF9waMwT zIQ=u}GvC0y|C*Z@Vf>%P7j8rH?Q}xn7S7IRFx(vs(!o|pb~Yqjmzd(J;_#q4y)%8Z z1MSUx^Q*Y?x4Q5J9RD@9_6Ev7On!f+)-UY_@>7}Vn`%HQ!A9${L4pJ3w5xiJ=CDj% z@3&U#|8O~QW`DbDmmw_h-pjK1b9mziu=%&$>6P})FP~iQ!FIORE7mqlw!Nm^CD9F7 zjj)`z&3^kBOj0CIvb`M4wH)$+7MbPNe5FlLObVnq>& z$T$*1K*YcZd7p~B$V`!?uv*BXvqA&N86W_0J}^abmL_WFlhu5F$mW6JwS2f&^h%?B zNGa5T)F6hzkr9y?Ks6{r zQ1XI|?9e%+{EXs=CP6I`GIqvfKnTQOl@eSiokkP|P%}n7CQSkRaE4TpC;$R6i1$Dd zNfAH@(E%6eM$GGIM6`p*01T=EQJ}?GVaR~`;?zeG4@!#onBg7+Avi<^NkI$}APQQb zG#DqYB*2KO7L;kGda4>DP)r~M_GnQIMFmwS!;D}Dt z1rxfi3(T>zs#~a+nH^iPz=T%GD1%V!BsLC=r>qlTRf7i50*YXW4Tcrg0BR5nKuVMt zHN*;0AUM@rv2Ssci*sJLV!{$sR3g-Y8i*Z4^$HO*sCslDDMCV;8h-?MAH`WLh(Ljq zh_9Z%9s+4#Fr}I`Acuh18R#scQAX8@!kkP-*^mWLBMO`WIBXx26@*HBs0dmHMM7jU z4qdDi=CM=pAliyVAs~|&Sz{bJ0uQi)n(71TVqWrK;N7{j=o0ZZtkxuMDS8MC6!8i* z$-HSM^EToNCyjxSbfN28U)qvzK%wgKg!6)Mt?8CCe{603M6$j=J$~!t&KeCa+r86q z`~Ah~wafW4Pi&aNLJ3(@r^63v?q<{c6zbH}1KqEt2lGLiZWIG)>Z5itD$}Qm z;RlbV|M>o+?X17ozW@t*W7h8NB+p##Wo(iJ+?rypr;l}h3l2|~->+sx-g|Day_`1R zelR|lWS?GJKgP*>YY%v%$o&?>a(?w>=VrMz+{v`pTkqGGmV9yQq50U136J&K&Wm$vi8bn*Jb;|s&~7jw6Mk|$~PcsO+CnVXnBq1i~L$NKg?tpN8%Jo|ut=c~AF%&&Yly;W5I!@cqO zCi{GEZDE!_NRPMI2Ujj!$}eQJuIuMvdjn37^_}~0yqKKGi6JwEv~Mx79HzFMWL>%# z?;dvZ0$$q|UHK=D)9rkaZmb!;mdVn_1&7rwGyX}^h6!9cXf^%u`~7v`U;LCE$N0~` z)ebrN>171+0k4m0I zt4NC}c4G+9$F3Ib6eDspM#(BC$~NURVN~nLngP=q1Bga}giOieNSN3fYfKr6QCHYO zny+USEM{?5dyO4J1-Qb*6`N(xFeI$=X8w3JH)>qsitS>1RFVQjSKtuh!xtO>XRR2737 z<0LOo=KXJ(?B7 z78nr-fK*AyXtbaSVk*QzRFD81a%w;snP7mNp$JF^s6>FQL`dwDG-ObM8DdNr7?qSc zN*-e#BZYuLnTQPpgbq;+sG|yDr3^p@B5N>!x!Mx&1mP}%FgXHaDNBMz9|A}O4C*wp z1Xe_ZP9Ta%2pYix8c^*NJsLtnb)qRMD6nFV#t~Iaa{}WyNn_UlRNx5EkS0#j;K@W5 zMr6PUqJSm>`wn47m>A3~lDEdEkR$dHB4Gz4f)YK&B}c9zPJM`BZY8L;0*H)aT;<9p ziC!TJf*6P)v{hGQmUF+asHj5GvbTZ}IRphIM2lz$&-NAxVTKVv6$Ld4sIo`k7@^g! zmf%%u535cEiBU906%Ze#JIkMAH0Z6VAQ6H-VF8MY2od?_<)=uP5`)Pa;0`3HZGhk` zCaHpAjAo870T>wFwrBTx1Zg3*5NimHbP{500X+}`G7y4*$|A{bZxip@!Gin|dQ~Kq2HFMQUg7C1JP1TcXkpMx z1_)6)V0A?zD0RC?eAjt%%b6;b`~(IECE1}S*=VlkYTmt;<5RBJYnFpmlERYgtq<$d z*XNu=I?Ru2W2$iN0D6Z>S76<5FSN1OnR$utlz#64T)0!1elHy*&&*)&l!linzk$6( z>k`&-s0i)_u=^CVP%(p-2AM*m&Rt{eFkcXLd;@8Q~I>Hd%e z?GDFH|0uSP;Js})eo}5efQ|Q(ig{@}vj*lywm69gKb_AuVEBo0<8i+BeQTfA{8zL; zgwu$F`&xehKN;cCRk;3Ju=}fe@L7EHf1-2u@aos>vKK$Nws<9l?OrjwkdL0QgZ1e0 zkZwTiqq{$!zkf0o)`tRY&a_W8>ZhA z)7On$eG2ese&^%Eg@8#1@EVz~Hp^Ea{^e}*N0|On`uqH;)CdQ^~2G%V_^_^&mI09rK1vFqSq-|mAp00Cx4osYm+u5R3YJgW7m+H$R z8x{%2XalX<)m$J+lfHwIOAAh;h!?Y*jaQ4=G4mq>{i571)AiV%Z(~zer>!SWGvgdc zfOd&)g=sxq)Q`>L@Zo&?F@?Mb)+#9?888e5N)I^zcbKH8IRP*Oxihajsj^UpS}+Jf zAOKa2L4$^f5X1$@P&3k*!U-~wv6K=56R{y;ODt#Mp5$zghnPmlIz1yc8L$?z7*qv^ zQARPGGjQ2qsuBe>f_NkpL)sXu9HdM+Cdw)n000D58YU7^G?SDy5JV$lMG}A3moQ4aVHw|?Aj zhnc%v>QeATF<9Rwag1D{8U-U9!3GK%AH?QDpLC6PY;uWuwRRmWM3VC`sCaS59aLh@ z`yO=i(V|&*<;CqC7-o_IIUe+qxal~h#nI}m2Q!!1YdhJ&;r#awPIfO8pE|!87U9;> zqHofdHwH&*?GK81-bPgX(p|N;K(?HYNw4a=+% zstz8T+wZ0}t}a}j>eAl(PSz87;RTr`{NCdvn%H}$gUjw#NOudo@g#J8iFG{wpn3JJ zs#Vxs=hKON?QJb|EZ%WEHa58iKeU6+NU5wF_A(Rgh=cdL!dxw)|?v5=L~**-7d zv-4*1c>Tbpz5aC90dDNIKEi8nQy=h?ukZ)p|HilKXPf*NudWw6hSvkI)}pNk*U*E* zqu)Qhm)QQMyO`!pa$yW*HrrprrrWr-sD}Lghe-y>mtX7+uHwOo*IsCk!2jm$_Iz9Z@}+HiBhmBH%K$7i z4NPbFXmR|pf2etW=(esK^X!OZ7wixuN(|y7MoZdKnDW7LJYGHCzjt`|KCbJw zYczT=jo?asBmwn8jTlr;V6j>pp3Fl7=m{#5G)|&~AfgC_$V8Ud7($znO^sD@7+q+j z3Xu^L1?rFkmtN07$NT!Yh_7Uz`kEJ^01^n~Y=x$xqi%n{yEWDEgqL{> z#mbT*F%mEV;}YX3afK#=D1jL4m?E9EexenyqAly(a7D%wd65|6H2NcfE~t7^LSXQy z#FP**p5>5%jT$Cq3^T2}OuDf{Fisn*KA$)+{^E1K+jQ z-k&=*?ak|bnOoIW-Cbxjx*FX88kPiTKr1SSBZ`t@LZhID;tZvUiP7lCghoM`0ZGw_ zLy5wWgg}ZE0TKW~G}@pYy4s*!d2_4k+Vq*ne`fE!*8IqtpBecl<7Aw3zO~+WJ&&1} znCnb!o@g0((WS_&j=RihX*t20ikI9g8JQ|;=cAR{6{>(4fz(*Zz=~A2^`Zd;l1pUj z*g7*)^Pr6AY%nA7QCx)Uj9N`G28od^B{P_kCRUKakfzig3y>o#yhBwP95InOZ~=0{ zDu~X42h^ak>CCmXPE;{bpx9|=1U3?8M@%62O8mgFnIw$}#vM!MxCDs7#@<+ny})ZS zz(7PGD47t3DfXg;s2z|TC8uVIR7n%V2xl2OBLxeVI$Cf(afkrWooaUylPb(mlO#?M zh6Bu-nQ3wfYjA6X1nE9d^jeqD1=ZBS&N2iOA@7i}hgusfC0z!ax9T+J%y7gR_soyY zOgU(%DNV>4PnKH)tC1JQa8w3TwS>$lJ39apDl#cDN+z9xCG%dKRdWFl=ynPyq4w9_xneX$C+!v zOVd?SHTH_kTqNzdOvSm-dv?8l@my7~K1NfZZm7Et*TG%LbXvM^gu!N0-CAV*-rdROPx-5Rwzca{FXoH$^u$3L+~eI%-o33m{YCDU z5BmA>T5sbE^87DnA9^a6Kk5ds(F_|`H9oIxKRry^pQQR&f6$}FSf2Ybc77{cJy|^b zQMvQBUizvnE~Ib%Q1e6gaOwA}<2WdM{=yX37rcF$?Iwn_*yoeQCQi>4^;@z0X6LTC z^2N0$-|OUyN7FfGjrM!&&WVSqx(w(=YiD}zU{P<+PM=w|j5bz#uc`ZL(_TaIQ^odI z;r>=Tc^>FeG685FM!&EY!r*Bf74P<}pXj*}^G?$@V}=Wnl}yXCL`Yr63_ z2_NSD|3J=vJ?{KcbMoQv>R*gceG4D?m9pBYiVwSufzb#f)k(s8)8(VbC*u;fGkTX; z<7%f58|U4d4SjuabdJ-rMY+%JON9fcqoGg@;}G>}2o52>(w0$q_CD?r%6>uv;!qX44HpR~1M1 z=XxPgW0hC+{uyk5>nKZ=bAQJDt}jD?5s5 z-l5JoCPh>=Qfo|>RAkd>f8QM~Cl6v9U<#{MTa^@3(je?TvzIKFoSaXRJE5Erfm(eG zh7`?2Q3RsIe(vS8(CuDvt0+sYNyB_Pn@np^5>C;%l*j&X+#41Hr44nf&K8{~u%XDj zxlD=7p_r}OX-(^yUCv0VUR@UT>|}9z(nL0*JkP443dvT{uZ|8^501NO6H+7Waw!)X zPMu?->}=8%cSQV%M3pjy3MY>QG7uvPf@jWzW?6QSOQ6iGmtv=}I{-gqHWJ4IPvji4 zR4ifw&G_u#mDq9Sh#iR|_9TzVy~|k+3|LhF)&K;flv8K3p&8xDz5()&kULHc+cBz*-a11gf2P zT42ejMsd4no>@vx60+G5t@rWc^>JS+$%()f);;P=w@SQZrj5n?~d!l@lfw<8jojh^V2q_7uDa7~oBs z7ORd%&@EF7P^HYQfQzt1loRD}T^E~xJ*`S($3HrARz0K4;r{px00N)%Atvv2KCU2jU=c86q@4FP=i!_ z@YXItGwmT|1Pf*{L~WxICBV3C=Qx#NZxGk3Wj6MGu;nSW$za44bVS-PErie)8Vz=; z7cb`TO`CuHXx4Mxr9o^a?(IE4V87X`wnCN1k}NB!$hLXBW$g*3k0|Ane0((T=-q`^ zmu{|OLjL7_HxBO8JLM7R3wz;eML&GqKdSUM+tA1IIk#9I^>>`T=kb>k=NeBkG+ zYCX}~P-&>8>h7NWn-iCf{f|AFpPt(1z8)`Y_M z$Ncbx-lcxeUtTvFCMnYKL;l8FZi+dtozQ_leYrOTefl}t&*clR%0{pGu^(T)u@?Tz zcjB{6`KNBJ6;HcvLvw}$9qk(KWB0+yufD&#(Hnhg`|@JC`h%nWb9H`UK5S)?U7XZ) zvHQxvEA&Dvt87I5wGA2HvUJfhkI4gl@QzI%PaZE0ht*))o#!r|Wb^*Izf|;=ak3ik z9Sn*$j)vP&K7Gg2$8d5kA{in=KxK6ImHFTK;^|g3{K-2PCwCWL{M^H>x_{H{EKch# zo zQSwz%))2Ms^f-W5Vl($}f3kmkaB_ct`uf4+gOkvnHr>(b>R@H7+I0rx>a0`k)E2~Z zI$Z8Qn%+O0#N*DL$>%f}2aQQ7!5e2Lj>UOlh&Q3c(lRB5rI-?u5N*aP0KWEW(PJy&1uShRt)CVs2pDE50F$EogHg-kM8Xxx#tLoQFCirkusGhh)Y8kdZ6Y zc@_4BkqPd`8%A5>D3P)bxgp&9q)+)70iCEZ(3Bt-Q z5kMYn9kU4SuxyJZPAigRhSYSnYC(VlArb{MRS_g%ILCeFK9m76zuc};B>mK`FCIA&%RRTHEnrq1Azu{bR<4A{ULH6V3JPL{b;Mb_&V z8q5|+nS>ErP#_qS36#lj#g9Fy5+e1KanZhIj}sjyY(I>*4tiTzX6H(msA=5PxB&M?NjG9xBs+~c#g^Nni*9wL>^TIX2DB4Giv-_)ZM}d{N&?{Kv<|ZsY&p@jO?`jRqRGA+=%Z`coO7Wd4Di zzN)`JPT`6Ag&e*0ez)w}_ouUG(f-NaxtHAVuj}whlz+lssNLH8a&Bn2ilyq&vF@B$ zoZIV9V)bMEoaCO;J}eGS`x-#>abz4|BXAGwbgzu&*z8`?PMHB09;FOZn? zx0=)M&K|A7-Ka({v-{m{^&FZ%??$h#2LEV!@)P#%U(KIz`JSp2<&s0ndL9=l*`Va5z|-SwgJ(N2ks4yNCWf-9RFv~{`-|C#>q+k>^g zTlw?3|C9__hjZN?@?u-|UvG}TIq%ohM{gtaFU9Ps8z`TnVTB-wkx(-NbChDXx_>%( zczk*^ogKXX_`x4M_;x$y~X_TNxe#( z8Tp0rG;zDTm+nnzGV^ibT<#XMiH(P?lK%29>J5R(g`t>2s?5a z0T6Y4gr268*dZp=UNm5{e!%W974m)L^zW zp2EbTl8h1A0tMq>4(3^j*f;|bk!UbY+7Y!(x%7Hc^?|YSqUlcRz?O(&Fqp9!jMxau z0_TxADcBV*V=6&v6iI?uM~Mj@W`?LVUv;ykIg&!W076OzvW&zeGcgi11uL;9s)$Es zqr?c{$hoP544`X}DPf<Ck1}xr<@6-rw}&64`oBbWJ3euI740YRg2E z07t9x@Zj|A>3-k!UD~meTIZ*|w#pW3W$4!XOEPaqMT!r3^X&x&?W1$c#q7kNY`CFd zXFbctTj=fJLLbYCo$Q)WGaf8opSm|5mYp@Djg^<|cvgApwr^v<+0ZEVyX=q#C-g~S5pI%>WZB##XbMtWCeD2=SrK>09Lv_?t zAX%nME1qNE_&lv45andROZnE?+i#M6Z5LhRmP&xI5+W)tBq6@IUsk-uF@e z-(NcY$g=ok?H+)qHWKEq^3mKOIb89X(t% z{qdun)Y0-nymmvL9(jYcnRY$vJKMb{Z@%f4p{a*Uk)GNH4O1M=k>^+aaGK`dtoM7X z{^oM0XkDG_#f!ALK{47=5gC1G~rc24udFZb?lxN$YVT4wpVYZzX!anI_b?%_c*kA5rfJ$!ri|Ngzlcl`Jlp16LZ zt5@f{IaMRtT2g)ApJvqC4(C=2&* z%5x|S3N5rFHa6zC6g_qC?N9DM-rGBvuTEC8y$6fq_rz+6p{={F({`n^(8WiExQ`cjn}<;Gebe6!5!re00vwR)}#sfylOHt<0urbm0L z*B-An^89>ON)u93->>UeQWyjptRU0mCh;)bdndi!Ag&}YOmc$|IUz(?1l5GOICYMl z!v|15BaTbN6Sdk*i5LObb0~w1k6RB9I+NgP=f`pI}6ftUNfkNUl(?VJzof<%r zle(frapk-haw3^SnQ=cCOEE=tsuVROOPWqw+pWQ+R6~#&5`)3QT4bpzBlW;G zVjY3NP~lFb6^a?O00Z9W4DrDr$6QLxS#zVFAqDHYl$eq*Scokr?!idOjB;^3mwV}% zz?3w$T^*>lGrpn1B9LH-tYhoMSvH^oQi$YGNVF7*j19~T&LBBBS5zrtImN=~lDSNU zNJzzkQ)HRTOMyyE6iLo_paz&CL^O$GQb36k!wkA2B;m}MfezZhh*;RG6y8}yl)(z3 zfzXh&K!SB_K~keBbk+t!&=gh0k>pHHNW_dy6LkSCI;K|6>UkPCCAOp%%vP*(GJ|>J zjHpM#>P^r3?=&NKB&f(X2%2#$nBbTICWA>#+-XbJ3X)vC_;lnzXqi{03uA$1tj?)- zdcxRe%_y(XE6n;XRp_Y6GBe+)=89f@w!KVRs^#%el-s5(Ri7W562< z_?5G{qd?XXJgA~{TrVE)9}Kc;JlaS|VAQaMn6*u61A>-Off%_~DweT-3E4->p3Ae9 zt9lx@HRWK1tm?znJR>ESAFk5T@iL!cyX_Uxg=IHbVAPgzq3!W}yRXINe0sDzdgEYy z6ZYK3-AC)$H;Z+T&Fy?(z4hp?Zz0~IrjHun$J7102h_*uPVc3M8s2Eq2KrCtTT|hC z8gHWaGp;z8&%dzOdlKP~4AK{i!QT}66fXQ#x>>+IhGelFcaJ?DC3)&~T6@Lay_r9L z!Ck*+SH9<_TiH8TajitST)eq!;rHt;is{05f3`WeSM^hs$7}|*vQ5IsvVPCffU4J{ z``z~5v(?Ejl>G?ls+V66`=39)c?X++u6J(WiW8&zat-TTYt+5=j< zU%vaKJNab#&>Yv^%%s5m?QmfTU8}zRs0=^A^$XbfgxeeuC-^K*``ygZGWzKo&HVc_ zjNopRee+pMX@oTP$KE<=FVv`J6)k&M`0E+AH)wMM+(Tcj1r8idJm38F*!x@Q;8Xti zugDWG;mY5V<&VnVU*$XR!2f#j%B6Jgxs#9dur*lk&Bwj_gHIPEY6c>Nd81F8k0Xp@o=5rGwbK zx0tUjD7T8wm$$C?r?0_3fwHjB8iJ)jB~#-`PiOb`5AQzSeehuK{gcC!=>Z>4=kpMP zHchZLHBsZKrCpmf#xeG7Ig@6IdMe#K8AaygEV6-_mzZtCLl9DA-6WcX z?XisJ zPJo@QF@U+FQ)eD1&Gj6w~_Lb;>$ z%&+Asn$~6vBhw|)De20wIS890_Ed^=+5XA+?R<0CtIjEN>AZ8IWfrooL7NbP5Lb+) zcXf=j6%ESm@wyMrGZc!HI-q1aXy#w3PuD2F#lw@;>PxfZOWgl(@7%2D?)%9&uU_bF z?9%)@y1TQP|M0o-tFIjV>o31|p}+C`))PE0CMllV&TnrFmHZjE(<#l4G1V_C!@mm+EoHWzBi%1!UJypsBhqh^D#Z~#*VR;VKAHTl&jeE!c)vND5x6}LB=61>4 zAL3|+y$6^+(l_7Jj?**O6ZYLJU&=>bU;KD@6!~9%Q#K$!`*FX&X1}@{pAr9~AL^}r z#E&&TBn0d9~?LT@NqpFmOuN{+C?bbwA8PnV$IEfQ`pnMew8fV~eP_cx z)h~!hoK5H5gTr^R{ZQIMz0P}{`39!o|>gQL=v1KReK9T5?N%{Pyfuzj1t?tL;mh7tj0KJD@(eKr(C1lF%xn z*z$bN@17p+O&`2D+uK|0A0HmP_h`SHg%o_CFyW>P+P1J-Q)|8#!?0O!v(k3jbkkL& z6?5$f4 z3qz(LB~59GbP5xoVq+n8jyZEoAXaFpT|c1(&5cDV10qpJ1c8WXmW5J4$pK{I4Ml{6 zG+Q+%4aj+)GsKaDdcYe8^9@(zawaAdCd-_bKC7y-Vo`7aK+bgBb^X?ZM9EHr9z=M? zfXyk9LRDj`m+h+U05TF2M&?K%j%dtW7$uWLWSl|3;LL)gAWA+81)4L@gd#Bm4(!fc zg~T8y0_UC2SvX@#Ms;LNP3Mpa(IF+Im_==z%yO+2NlhK640WO=cB~6AB;!aRQ5H64 zFbKo|D4 zf>}qT!pR{-3o*7`YFe;R<_W@ZtZYU`a13LDV-!FR@>=4O%=C}OavX5z>@ zGeD5m!6jm?XoDdVR0C4@An5-xy9tnlzXb3UlH?J@Kb zD~t+I3u=fxg~B>Su_R|$wE#|lB{HKN=!4dvYi70D493Yg+G(`Cl#DqSamjo#F=O~~ z7cRz=Y8W8m^Ywf*uB;v=MjN^mkuj>UaQPf=)~=2hhhxq*d4p3^B1@VA_82pVi;Zp6 z?u7Hea&5}nC%N^Cc7rS5_N6=QWxGQ+(0LIS>0J5d`J(%By7aGHwzWpr`sWgcIjwDQ zJEmmKkuF#v!9U)c+&2DpSo>Mp*`#GndE2~ii`V;T1{r@fj{Zfxx{c)@ayy^vZv6F{pTfOACF^hM zxqo8^H#mHX#yyCn;|J`rL9aOUy>G?C z8}j%k^WLlTkAHJD-0Usa{4*mQU33Qz5MKt~Yr8k|QN6K--mM~> z)+gUNmL1^vad&UH`Soo57J5I2^99lZoGh5#&v>4@8;_%VFTV3szWDL{Lw9lY^Bk}6 zWhh61n+#6&&8@-!trKf%%wi?)U(d|#_%iqAI-!6A9 z72}Wbcn7vay%KOZ!xTDCmQy*OzjbuqgsRMv772S@yPHDK@yJ1aVJ;t4e%vdXZz=P%DgXHGOsb1JX7wBa!68*N%~zHwPD6`?6Qox zk{H-G?1k8YL$7Q{rHgG=t5+&j9L$JL%?`=Dr@}cR69PbF>Jt}`oFOEl@Ct<_%1X1o z7Swg5aIhTKCn;ExP+&PE+Z7C$HVH&f=18eQY>A^I5(p>*6(FZ5Aq4>-RZYe)g&{L5 z4V+Ari7X1w9H#6TmJ@jg@7X}foWMM>CwMrv1V|KWJk3B0$f00YC?g$EGZ#7nr(~j( zKoQg=>r{l`8Ide7vm@u2gjj^n974pNJ+T_6K-4nCVy2xL)L7L_wM*25qDH#}ny!s0 z%wsnR@I<8pWx~BQbLj{jLkdF2+_EHts>GCng@$M)oH?sRz+~dVMw~c-BMFcJIoJ{9 zYGt53wfBjb;XRvxNlXZi78Iw{l6TJg3Cp2(y*#ZuHzeCIMbc!(s;rg)6{#l{b_69r z%p6oyUGjO?NN7M%kVdeYClmxx%QckRTI$-QAu$0&6o>;l*z!fT5 zN6b16?8KR7tc7G&R%+HFHp-kzhXJGnn20HQs8iA;sx0OOWDqig3R!B97Nl(A)FzNA zYcJATr;^Ag9y=L&$zt-sys@WF5*eCC9<1u^{^-v3xxGBCd@xHU)`t0Ww+1Wl!D;m4?)l5{;mUnwSITNJ zxVSudzxc}Miwo*L^pU>*NWZz{iy`$lISfcWrtRYEtI4o(pIj^dpx*zlm-lXOjsM8` ztFuS)+3&YIgX|~o^bfWB(#gD7_djyJ_wBpK|Kax^etKv8i?^D5U3ab(3XtVx@MUxgM zUAj*xEu=bdr^|!y!{lnhGKNXFO4fn`v6Y~{)+yP`KN2X(iY#JL@s+$YX6-pb2Cu~8O}4)-72dpuiGC*E;ZYC$c@ zlDd3#vOe7}_m0X(nYRihB`Ol4<5YO+ITRw`46I&Ih#@27Tv%3Ut7+9m66bxU%<2S^ z5$Bv4S7uB?B#yaaSz}jm2J#@P;D(4NnjWM`t-9K3XECcXH(*aJK4TQbxrrZbSV*whPhZ$4y)G`p`ENKNp9kZ~`NRI^&=Y^x= z==dz+H74+x8j6J2v1Y1XiH#T#C8yekuH1j+l33aAzA1BSSE>7Kt*!$WxIgAtMl+kT)Xp z>bJ2K&s9l3Qj~p zIhp2ZQ6xoDBw=#wIA=Ft6EYEA&p$uaT`3D z<6x=*X{EhI-E6zVD|+mua>Ks*T=Q0amUz7+8+l9=d)7p^n5Fl!h+!wCgmGCl+MmbO z7B@E}e`j8N^GI&-^2Jh5S9jl?O)typPNloiy|Jh+O8?oR|3bC)_x<1_ZtX8-y~~_R zTiY_eU@MPBqHC)rESfjgip9BdZHX(>BAg$sF6B>uKc4$sI=n*K0CZaUi>~`uen5<%i2b4cHj%IpkO5#{1Iyvc+3C{bRCozq8+3 zJh+DT=X=+G(A)mUZh6P5KV$s@I;X9uD~ksXyM?>(MY;1U9@nztPx8(K?7ZUV!}!{z z=?fLI&C#5OYp40zB^!RkwhH1URTftgj+hR`Wv|oV>p0qkJ>zcwrkwxpaPk6<{ttZf zRg8X(UcJo+KZ6&(gWLa1s!JrF(9GK$*f^#69{-?i_pJW#1KxVAdhZg?KN4?^Fj{l8 z+fBZ=-0Y*dQY;R#_4n9s*ydGRbLJFX+wIkLMnrjbc(8os-to->*Dnl*lTuy-?%?Fl zIDD-+|JN4Xi~8Pw#up#)wcqgmvhV){edMYQFVd9_YfG%Ai^u!N?=fQ4!%I5)_0{Q* zWBGq^x4ucY|8~)CgohtJxi*F`$43)S`|Yg~qpjlH)ceOm2j&hm_Gq!r?sIhGpSe39 z^~JwU*=4mJlEF7Hhmdn|nobYn>0>`K%QXp2*Kt;_hRe1G@2Q$(>9B08s_GSGoGo?Q z-A8&0Zri(?UQ1hMVRG;I`1R?LbJ>Pt6=_|uoQCO9wzj`_Y5zevEAloY)<_gBBub^o zz)8X4i4Dofa#)3A#xN8~OHBc`qUbXexi}JUL|{@WQyQoe98p;bMnaX(CC>mgrICdV zO%F7^Y35+LXe|1Y_t+=Pkg4{PI(6H~E7Q7M?1IWiB< zEHh<;47}UOr~t^EC(41`$b-RVVhnQxaiGkOfn75XsVs}$#WDkV2A@rqlE?=*LPqd0 zNhD%pA(xBq#U%mnV44RWqz~YUXMHvgI5J1%$(77Kah@ab zDRIu6E181lN~a(vD4C5-#lQ^FjCo8_M3b|{jxw2&!Ksw+8LJb`Bl=yQAvewwr_Nat zPg3R~R834ofzRGYl$-U`0ji`7b4XM00dXeWBbc&UrbOw?dCXa1T3LIx!eA$mY`|D& zRz@Rsb|!7|^p!gxc8+5YhcGdgl)N!>NI6+AR)oHqfIwynOG?QonI%J_3g~lC zjUid;f_7+#JLWtkmnd2yL&*?_Hcz2+AP;R*7p!5&k_jtZ5YoD!Nwd^@kGerU+LjAd zS6t8%fkJE2xn5mrW8%YjPv@SPBcJ2!6BJ94qQjV*^N?nMEOY)uc6myp!H0@w|_k!+EXW6a| z2M@~ijevZGR5%;uMPY;U-Etu>|3MC3T)1<>(tG2<_LjICPwDyVnv2mX9W7;&$MLv( z{9X6Oe^p#9{m*6m~`|+oH-=*na-hF(@Mjszs9{2KMO~;k88aJ$g8w7Cs z=-9pcARjtBciFU-_g~A`JU;nCI2`c5dCiSLKlfpN?D3E8+MSv|bHV3NX*?GuQ@nh{ z$??i-h?zfKrAD?~vq0#VgJNqqE|X-0;MI9;hkWE^vvk`}<>kj|?W$4^@{nZGSrncy@1MriBs@5I z|FzxM?wy{@)3P}{(!0kE#mO34ud6e~I1}}Va`vtGmDD&sk;k(xoV29Tkd%{ofCwH$ zOpXBt3y=wy;s@-;Vj>JdAyNnrI2(yeQZz0(CgG%JY*dJ3-fP~A`C2yI8I63u9Ba`m z+gWUuaXE_%2L?p75Z@bbm+PDPs+*oHC#|BZDBJQynMnv}>(kTeqvf&`SDACpxuNUQ zQQbX$(BD5U_KKz{YD+7!6{}Hl!rpo3*~lqUjkpB$;Wi*@J}Nndm>H=EXE~&BV!1OU z{eb>&z=~u=DA|oALtcdI!)%Bg2})M8bC(uiL*0) zwn~*;A7mtvC?~Ey$n2>jYe-7xMvlNPaZcnw3NQf*Vdf#zfK5c$2|Gugb8t_gExJ`A zGbU3*RCe$=8wBdux@ZkM$uqB+z&nr=xL|ckOGIXMV&oI+T=h_?AqJbBr3S>pvd%m{ zGbI@jGqEv~6BRhMtfM>t89yT2b=b9)q2%*Y@Zj8*josO7#_4^muu1IeBeL1-Iio zzA~@BdDLx#@AUXp#TS={8z`U2b{-#>FF(j`+a!YrcdWjzviC!H*)zo z^MA(H)@|C^U}=*%ULrnay8I?>y^=Lbk5aSKNAG%ZR5p9@^gN-_ebmEHBsDg}43ais*CY_PdMrrIVu}>^uTe7rMD9 zGRyONIta77Ya3tM+|JOunf1!`P1n1;nm?YLgb|9Z{K9!;PcgnzhhIINKaJuSd$;eG z=fC0xPh$HQ=(3PKqK&0a+w`3=j?c^af5jL6E@hua{a0x4R$BXC)6vs-`xoraE^dFf zXa-e&L7rYie}^xs*~}=mN4iwPLh^SW&gXCM^)AExaC!AL?q2>{t}hSk=RS*P|4GlDtd4)2Zx!I4 zKf1R%eCcTY8O;B!e*X3H`hS;gJVW`P!^LRh$H>Q&c7T)R8;_=ghP)1r_sQ-p=n2@* z^ftcYdcUNLPo=y6w|Mz`*!ksNyyOm^uh&*st9yfCwKnqSNarpsy)82;oDHL1ep2OU zEx&41zXzIpHXYiX&y8Qopg-;1`U-SyZaP!}?G@P*~R za6Vrxy7{5V;y4S_mZ-rQ<$(=gFkk>PnJ`3%#R~B~SB^Msuz{e&1tBLM6K_z~AT1EY zl2hYEUMV9doJIB>_eT|Gw)o9c4I=SU+db(JZ0iL=CBRW z1#$uCAVS0fAiad5zw3Vf~vG1f8Kwzc9nL5Nr@b3^YqYvSvlEks*j8eHW z$Ep;qQXLotOD!y#c4nDc4v?@=F-&nixG@2qAsjJuq3<;J+6sk_UNWRlS*DagaZYp2af?`Uh$Y7aMLLvf#5WG?z z#@Yqb1kWL-WE2ydk~7Xs3q=A)6g|aIIjVKam0;jmi33SPvLqR@YzZgNZSKJa)MQ{I zR`Qw+Gz~1V8Cgc6=A~~oR_>rn4lLw^lBLdkPShh&aI^x6V3av4oO+NqLo$mg2JKAM zGdWI-$T;%uVIgpoGt*gf8(bnAvyGU}N_@mBqK=S(Oc9U=SR|}6TI5V-oJcK@wG?Yg zEy>Lb*R*Rb22DXNz?g_ArPLutB^V*y4& zAG8DqwXQXt>1qj%Qo+%Q)bH3@mA!bicmHVm{d)(X!3))e`zP^#d^CM3D}UnFdVStM z_EwFuwQ=7x3kJ$4V`-E^N7K{EH=3hzE#DXnmXo7*+lQm|{&s%O(=yvW8uWcJ?{)2i zRJ?uK^dG-}PPMCUG`;O?rPU6}#mkgkGgsL0p}oF~nc&)(Ca=(Ef15Un>@z>qdp9=! z)BC4)s{B(Mqx<3XSG&iXVfcKwlsf;qOV_vPW-bz$L9=c(Y3I|qn;+-h$;qki`(BA| zz2ErfhwcRB>TtMnC&jJZ1u(k@U3Ohqg+ombbk)WcTWOn@dfUS*S$V#1Wv?jQoKC*U zyVlFa4c2qM*>k5Sr$=#jaG@7udpmQtZ=kqHI~mt49V*YWw4Ha4cjZgp&$mH8@(~N$ zbh67Xre|)2*H-p-zlw`Z@kg(WIg^KrI4pRu?otQs>}0a~?(S4lew_yor+dG^7vitp)ubX**M=6i^+J8wHJ5KiRt1Ab^gi|M_VK0qv)%qx^tMLZ z=W;(XOq`tO7&ME*E&_oXJ-M>*Y%xFR0~a=Gyio&G!uE zvRqu<8!UR#kN1|dLuv|#3YjGb zI8KgK#DT=5VAo@VYBDnzgL*`sXabrTmy(U0MberGk}*+V{TkB3;*k-Aof8GfID;ct z+acA+!IcvrQ&PZD1|ujlkvFnqrajR}5HfNsnPoghOFW-S z112X~WD7o0YMcf(<78%Pwy?BNR9!b{+FolzwHT=Bvc#4$?j;?j7+`gR#i*vyQZlGn zG7+mwIa#;i;JEK|m#ybQnq_&CuJenv+;0!k()(l7aN?NW(dL`o>}sBVsORomctif6 z>gOBz?Z>$NjvL;}`6pc8o1bd2W`4~Anqw?R92I)d)d#EP(+!4Hy+5F~sGn#srv9ub zLe$IK7(QFrynX!E;h;d;S-L@!)6`Vq8cIGVQAE+!A;r7`L& zcKf%}`0s@WKSalWL2kWgPkz<)Ht^Er`c8*)P46@eijY0waCt~O1vJ1@d*~OTrBzex z9;dLkx}+Ekdv&12LCr(xt?c;lEel&O^fEWcl6D;rQ#fBiM_G!G|#=(yZp+x|-7uUqxgHad@XK*PN> zc`JRtphw&M>EGZd{szxK);83%?U zH=U939`)bz2V1iDG@kq&{K((Q&cB%5{mXdzn>hcs53Zg^1haDd^OwF$N~E7`3{(NR}Z!A+CR`RMTN zd;9kv?@t~d9v?L{vGsSl@PoF=pcfHXtuZ~6e9vb^QH=cX+*B{`l}D%VKCIu3X2DP^ zwUC%7DU{A&_DV>^LN0?ilU$szJDU?El7Y;K6w(SUJ(h$Pv<}+=V45`LjDk^cD5bG7 zZwp$GpV82*R{1jS#c*Fk&N}3FQRLGCW3w&w)5UVKG$JM@pUg)-2KJD+5wmz% zx3q0I()7^a)DCGtie*f2yxZXz{#+*4Dd>x*pbV@C4|qeM|0N|&dey062%YZAKio9GZR=z z;G{lSuBZq}EvUA_t?)6~J~U4lB&>tZ8M;naDK%y*OFsE5azILCooRzKH9jIvVj_To z)mr66s@k@Rolpv#m{OF~xs<$uV(7YPiNqY4ilL0!k4UPGax#n1mMFk!NVb9t@PL{| zpf+8oLQ|GBk+w3AYL+Y}G@Z1OqLHW;W~?EpPpLzcJfte-n!1qUveS8^&df82NX{NH z6`@AmP(l(h7RR0r$v=cE*@k3DN=Z2eiIO??S&?bX6U@3U4Sd;-%STYMP(V$7y zl0^Z=q)W#w;K`S2$wIVNlZE6}bBbnAJIgB+5|EYc37y{U>b13aDIoJj56OGY%WANh zuZy7HOK#3~AKg*+Xub+?Ln8nHfB;EEK~x%F=?!*9CETU$a@5P)z{AYl*(z|(Ngoz9 zis77H+OoSxi{HIB+aBsum+Q`Q!-*DAWZ*T2!{rUFz@~r>ktDVD2zcW}2M&*x< z*Is__@PB*#?)hxvPVaUK^2pUUuep!yl_J`U`AVw)j+ zY^OaQ$QR%B=hgk>Q^l8O^}qX#(~sx9KXl_^x!X z*Q^}ae2L=;opz~L#&_S4|Ngh!Gs1u3MRy4IJ748Z(8oVb#}>Z!)vil&>xYWP4f(p* zb}3J8Wodw>w5GIaX`a%-{Poud z&g-)uO^b8scaOVkR(@<}4VxL4SeP!L^TdlNRmsH~pFeNc&zpK(kTQv@-MprGq{Zoc zv3v5`-u~T_-J>@TC*M29+@iUrYn3}16Z#+l95VBn%X{O@4nRO=;Q#TZmuQwPln%7_l|&Rkf(3@{OOZEDV%SsxG~Qw&i9me5m*X>~%2LL~DJ(3EsFT});(%3NL+j(zS5 zCK5wNTrz_|1k)5_OtD*X=nc!^U?5~_$v^^D1t-%$9G$ZwLvEUoVgNBSIf7>*L!mYX zl>~_qEin;0iiu3mC{7^28?7n&%9&+QT_vcVo_PEJDyZN;J`qZrHr%%qFb${8KS6ws*2l4`4nmLjyHGmU(5E6wKQK;q2oEYwt1 z^%^B+g$(;yrzwO;t1iuI2g|%9HYMvsXOa}Lr2vk}EX34WGBn9GTrp>kGU9@el5`Qr zH9cw+Ol6jNU*&lMB!g{}jbTWtA+&L|jDpNd(JfP)BeLM^f)DF{9C1Q1zC+GkO<7zrtw#tvvG^Og!zWkjJBxdr>IL{*X!6hbh9Ig)~W zMVvE`xs=hd(V?jqsz9N$WgxRC7IT-%EGZ%3Oy=PMA!MiB;!%7fR%Xc+l8=VDd56;H zM9y3lb;F7WEH6tory@|hQ&#Kl{5Fbb-MK*ikg6@C+gSDy4P%+i%7e#ScCwM--fH;j z(qGD_PxsubcI_+HJ6E1QRa9-*nW(MulTLbrQyU+>HO?Nd^~=|B>ANL*Yi?9-J8TSO z^So=v(qAl(my@?P=x{Ty-j3<}>76xiH~dXWqb}MGFMlW-+^ze+cFIrTBvdb4<`@be9O{s7yLvf5>jN?dAF)ymzmes5!W zImh+2QRUXNy4EOA z^rSBb1HN5hyyfzBq;+ctm^$v=?N+bVCqv+T;l75_V{tkSpVdzZ4)j};&OE^hs1t~dLqpYnHBs80MT z)h~6&mwOmp9p&?0anGH*jsB0(_Q2|3%gOTI;ptI@Zl~D%t#JFluQy+$ga0HOe>GkH zYhn6n9R25frdggssf)IFXtir>2MpOnV|1U$OG*c<+Wi z{;}@4Z_%Y+t)^Y7v)Sej==omtM$q4x9=w3~7s{uP{q?VA{T<@uoMI7|OPa*JlQ-U(T|Zc|~>aD7H0 zI@M0k6lc^T3!Y;h#$~lrWb5S4QJF7JZ5||A9vMQ7*HO{G zQ1pD#nD}J5JYDic=*YumT9hqQS?YHG=uz+edN!G!#FL({)~d42d3rV|Qc)81nG;e3 zGNMdOJvwJWBxSx3>5+gC!A`lNL_!_QKFO|d&gjEJ!pbN@GifSVZq!1T6f@z{BLP9l zkdkSgQmg4mc{ftUG!nouWds7C#%6*haNRgY@(g2*j#owtFiYknYt)ud7R%X`QX(<} zIXcb!VO}ujnaf>HnRAJ?m1c%G=eErAzAGKGCvV1)GA0vd5|E=rh!CN?;Bx5D$I!Y} zOc7>gF&QEdl~f&w1*S1XU5I$+axc!aD@aF_DxXO%=%BSo#XMx|jF!TlTu$C`>7^f0 zbO2HyYgLo65i>hsfk3@MBzj7@5W^7<^K4vFZ9GZrRErc&4F^;UH~?)xlD|i!6gdX4 zBk}B=lo^~Fsab-?WEOy=YNj1=Ed+*RWXy#pDTzu}QZq0}U_y?Wd(@6C1eMxkW^w}O zo%O`X6s7`pWCS8}AW|VCCQbsEGiM$mQaG!;wTznVk?CDnC9=U~uAxjJOWHS#1$|FR zHE3GG&B(;5Cz~WYG&4|UTp*1Q#-;<6(5;#{ami&VIkXwb*rGRq^TY+&5Lzb6l6XUg zRK?f}DT}EvEzFn{04$I)gTN_SN>R-cJrkaB*GC&fsW9OWtJde4lab`S*8>(oB5EENsf@K;@ z=}e*#;MtTROp&5V@+lq7Pn`@e`E#u0&=18~#<5e=AQYr1S=XstBi?WewybhjhecOY zr|@gIypH(_4^Gk0fh!&y?Phzg^mV%I6$`}P{LxUNJ#op}VR2YzWoFm6s3CbfS-o*| zeslk{?tSlI?HbA#F0W_9GIYpU`nhik>6qs8$$ou!nDRFkgS9T5AEh+$_g?XXHeM>i z>L8omFQcCNi;q&CpFSL}v;X+>RsK;I&zX2+Iod8^p5D6K{FB#Kqq6wS&C&kSe&wz1 zdXfFa?ZHFBznFGwL;2B5<@cM_e{%om#WeaO)s?bW+8QDX-;K4(uUxLKpxn4i^W(5P zS=NVjnk`Q{AJgFcST0_2uFCVgQcN`-+&ko7dy8*)_sR1`j0hd3_xb&oxwhIa%`-0C z@t9VNYH&byD50 z@OW`LpQS}Js%QBsMU*ikFI>3XvXPcmk@ZJ;obT5U-+#MZzUPA@%R8U}N8-}w!(7ki zUx=s}KA3??B}?ohqwy3yvZQ5#8aiW7E@NV1Gfa(UKu>9;2zJKUp%tjhv#u!m#b{i3 zB?*{y^Ie_kGVweiWR!|(RIIJ%YRO`l&X@NWt3rHM$_>>fn?GN~Ao z0U~gms}{;k>>+*6y^LGMGQj|tMb!joA{Q|hDV-q8Q9!_%nFcg{I4B^MPFNR?d7tG0-<+D_-F zhHi7iwTn2LP%0dH1n(td?#yZn(3Uisno%pRaSoERGnURVH<@1+AXZ0iRjwcQFiRGK zR*6Q@CCW^E0TyG5kwOk9xas8s=*uuKNXgyUzJCfF* zV*bm@wO>rv5!PIGS}*qIlY+YO;NGj~zBJ>4%d4Z}5r^-zvr8nOE?m8u zzIWW;g50VKoj>Xh-rp42T#pCiYj?}*m(YI^Rj1)m8s)Ga?el6HW?L(0*ZaYOHV^At zBhc-wZ~gzhjm{ZI%X72J=5xGrM7`H& z>pSkP8`;T^y64`~YhSe0E!q8XzCMkE_tV}bzIVsnxz8W}gW_th*Wbx5tfAha&}mpE zTt@Yg{KnnTe0F{^=}xBI$t>#SFg+-fAx?qq4-3vckJ{HbA7m*xlPmJwdwE*+E4bwTbX`V{TvwV9EZjOR& zRi@<#M{Cr*K3lwW*jnN_K`>TVK$>GC;qaPg2+tZ~UE~b0UBDJyCr98F>r%}r2sv^&-zNq)gqtU$GK5!4; zefhP!ulY{aA)R4i5)B3$5;lx9S@=N2PA~(Lx-7aPvIOQxWN=kNKcNHF#2HgbV#I-H z23in})Han{!%Af(ym06|T9?K}Z=&! zG!R|`Mk;adpuIoaorQO8B4stsN@cXjlNywW2*i!>Lfk1Z0p%n;%0M7;8BxJ*s5S=8 ziBHHuz@ErR5^tJZv!V4s6orU@cNWiak$jqMj&+V}(Ep(wx{MW>97rW5F^NWRmw>-mj{S!N!#6p{<0- zgQBPC3o%z(DIz8r37xHi1!n*WN`lTi(!^(^Xs9F0(X~OlmJEOhJR#?q(TQ@OXoIS= z;}`KHnO26rRvJSH+)}DZPoo`D+$i(QvNcP3BrS3XlLKW|n5IOjWm{1)0~eNjxJtBxOzvV^&HlrN;6i#Wf8%*dQ^bF6kNn*$4{6XqEsLks^1TIVEyfbteKmdW0LrYvv*PE+76)~BpEEq&K%)`5*9~cb>0J+NhjQ7oYXXe zU_t}}h#^m1*-;8gY7BM|G$zZWg6N1evIR^PCs2^f7@gDTPyr5FD?%i6N&yXF0$oK* zY6?q46Sz|()nsaQ)Vei-IY(w3EiD7;5RIU0jnOJWBXLR=l64e4ry_NMx-M{oYR0~t zw!X^p&1!(kY$Osrno^=0M3GGtK{zaT$&S&r7 z(fN(hdha6VZMSjS9~Y&L=JVs1!h>F@E*)+j&JTXT_s+ZZZMv~pm&5-bPk$C{?UtQo zV(%DZ&iOT~-TmM9|7r7_lPCKWDI^j}hyn!F7~3?a2qLO%pnQP|U$`!8BFa(5lmWQ_ zxf+8hl?yvSsEj2cGNnv4sqFha{rS)N`~B~J@6~_bH|HF~7d!FYx>##PtQBj0%{$)l zJc{ue?hbHpmLD!tUZ>|b`B5|dy`z)$QT;R5wkC1)`0Qweawm>rTwvpLc{zl7FwZC7 zb-UwwFx1QIA%v^8e#15gW^*j($llg!``{k^>i76^-~IeY!r{XG>+jG7wx53)Pb&VG z&$t)!r#8~@1NX~c3@^FzkG(n=T@P-{TF`1mtFxTvNwg@tVQyx5c(hk^Z!bDncTd}o zl$(F7e?fcm>`%YlEgoUfX!OL?rkIv%>qQ^+e8&Cco*W+j`t-@xPVdrq+hyx|(G|s7 z9!`%Yzq9|e8ubVDMTxpxUvAvsjdhQNtmu56`ZRgM2j7>)$>Co4q$>yg#pU%Vm#?O{ zM&I3~A?!1s#J3Rtqu=X3wW|O8^PBbUU>CLO!#gYzQnbCv>`U`AHyC_wW9MwK__e*g z>r}tWoB4Q^%hS~;Jb5;-b~3ngP~R+?Fxp{$?Fx!p;4#S2xZ+%BHO}9E(EKM~SbWA; zfA(W*$7}ShMH+be=%(y;^m~uFPWZ@OEHC>%n97dGt2cx0P*>&C1AXJMX6NqqisLvv zSUV_I<&&?D`ab;3=ga$;{=Kj6-=)EydF9f#)85~B`u6j-w%yzumEonAt#?yV+t~sK zb83uA;;kWWUb13nLS~svsh#um$mKa{gT3X!Zu@xu>CxhYli7SRPpg!hxr^F#?WD`I zL}y(UU5Vg_{qg#knKPp~Ib7U-{KKOU-bf#akkMUufY%DOJB(_jN9yBeZ!nX;#{J zj)lRqy5U-HXRVKAlV*$KW6Q@vDjp;&l}S2Gj~<2(EE`d;SBKI&B2qDkniv(3lF)NJ z^y&ficpm?Ix?tBHkkx zk}66H%IK4o$eBn5R*03^a+^}O7ne^u4kTM-OyIz4PD@LjR8(hUZ53t)%|=EZL^u}! zJ2N8(hKDgSjLo`zTRc@z=3qc-%h(R$s%PBe5S+%!X-|c`7@T(!NSrX68g<#)Y^um) znUP6BRKfyT%iV&UCs#8WAu*rxDbtS|-2ze-HkV3LPm)}boabKUhKQQm2!I&c^E@sx zH!4fl7YdHDNb=bmN^oY^ab<93x-?yw21Ttb!~#e*$rRX?%mTo`nH-3P+0npNV@F`g zoLv)TLB<}Qh{&-HAT@Qc3E+Z=lbYab%U2LDGBY?4wTzWB6Jj<6GTF+xxtuE-(S{I9 zMN;Zi$b@l@STT9w)`pQPk$G4q#iC7s1F}u8RX2A5YI6uM9g$1 zwnsS|93;acaB6mfWJUy9MP0SzJQXD+&26$}Zd=XDQ05h|0I``PGbJ^jIVR*bS&LW; zHbQRUkEglr_r@D*qoUaE_g7A)E+*A3JD*d1K9lDBjP^4Idwl{JruDVS7}0T3gT6P3_c0OXYIjkd0JCD^5yq1`aNqug4Mr6Yv0M6|9zUjjN|`6ww|^8)!FVQ1|KP2{i3`5 zOLE~g4F4SMY-&@htm5*do6Pe`gSun4lEDD0+tvO8{m1Q^Rb%bDUVk)_p}+U#{G)$m z%gUcqef%h+1QU355)b0)$gQ5A;$k~!*NRl-yEXhpKhx!8e)`frdd~)jwU0jDUB}u> z#bDi+P6~ww$kD0e)9=kDzjZh)5pRt?SQNv@`Gu?SKjrJ)nS9}>{UlBPYIWgv>68DN zTzih`e`=R6q8#x+5TiYf>3;0)&KRreYa4QOG5+KpuHLIN`%_6b3*c5c*^S3vI`elh z_=UkB^e!KS&8y0v)@s~5%ZpgqWXM}%tS)fzMZEqGc>81i>3=8}7dCtkk2dYyJNeRM ztG-REyP#i?Ycsj_4es4C`6-hMi;k+t)V|N(=`G(_Ke@Sv{_}(St5W>KZvXRk^uKp6 z{{e3QoiKZ)`|h7R`uJ14_}z^Q&E|MoS1;@67wkf9lNrwDXlFQv^~5e;hI^idgl2vY z#^8Kp`b4=|KF!m^_HaKPJ^0|{^o^rQTEH#(vS_NMpTy=+;~Dc&C{c#sbzNi!Jw9sq zS$F?~x4!@Wo9T=Onbljv91RA53`7ZNVI3j!IWy3_cfosgMRpaD5wa&M$rxsUF__pB zgq$H8X$NEwn`Sc*2NutscNHTdCkvg!0NFeFKLu1UcYU5&F%oekzqdLW&5<$tV z%&3$Cp&%n6bAqnJ%EZ)>!VF4PL_Z5@7R4AgBAp^0@ydr#IhTk!^W5gpaY1mLl?}{5 zHno(v)BMD_`-N;dx&tDBASD#GB+Z3!n{<}WH`5g2#gpXdqPf^|j9@R$F)1iPv*!GS zYJ2mYpOj%We7_bIvVq`%0yrO%GpK`P zK+dsZZO1F=mu}F@Ipx@OYMlXrxg;HcH_Zw%Ba3E@X+l($4%~{f;I-s#(&*!2V{@>6 z*-?Kpt{KaCB#mTK)q!D5lo*|4iEX)HX^NBRANl!{P)N9SVQ`}9N2~d^^gdH>@9nnV zd*irY=*2Dfz2*G>xH!I3jy^lQ%C0;ru|29jUaW&EHh@;aFZQ3xyFU!e=A;|!_l^tw zVBlN2H1ew^eEXqXXaDmzdf&Of_{U#9d|v9$tZ(iguKvZ|@pHrePh8z8t5B7Ou~xM; zhd6n}r|sgAOsx$#t$k}-8=4B5?y=W=>u#KS`QCdWucm9;$2%JZ7sEj%cVD93XQ_V~ zi>V$R<)hSUI4{`PmqvhJo%en+*`fozieBj%_f-6shI*(NM$;- zeocMPO@nzvRM|$;1+NeJ;V_Q1`)`pDQ(^x7S(W2a!?^%w5xq>3X?uKgu1NyqrPl9 zJYJl9t2r7*8-TsA%W~Q~xVP~**QuaDQM{50g#Zfd0|P(?1jf*$u}$g3@(Ccs1rtl* zT%t0e5;aLimLSnUnONAIBMew;;ugH*eCpyMlah19PJ(kem{w*)YOKQQT-h)Bqas_1 zIVy~xFj8Y$}6m$l#R%NLUR*DBDk6pg=ADP2_Zv>D1#%+$cWW)wqyn+Fqt#MBIz_55ZJmlTFmXdTb;BZ zg0P%{CZ4m1K}{P@cHU~5$9C4O*qT5@W)8BRcs&V~q;)n@g&L_MCxaRis3V&pPtUgk z#Ldfk*-ruvpyas|G6tA>j3LwcZEg!%S|VE@d*&CeX@gZcz4+ z&=R*)C?Qj!ra~v{C2zDYiY+Zad3aJb9?{I zk32eje6skVwP%$(Wl}Ov&fvY5h`2<%Pt`*^xKZ0@>W@sY_Mt!6(r;d0-6Ra9et)Mv zzUA(;xU?I5UH1EKOOQOOj8mhBAD%q;;gez=YcH)W`jwE~O&F&zS&L6b(~TikR|flE z8qhC>_&IF+r&!-qo~sn_6|}^D%^TlsdS9F$TnpW&%a8vXJpW71Z-&E<%bgF<|Au?> zhQ0ae=2MJYzISo0Ux^u)yCeI2j<{lU%car#`*e>D$2o_i%u7hKF~xxj1+nII1^ z+Q6Ep1agtoc$GD$T*TR-p6$(#PL2+z`)AGWyqnHbS_-vGYo;)O+t!JC*9-M1v`&I? zS+}vAw;w)y=Z$;s(gbUnTgOx8WXO7c968TV&4~1z{j5Y_qQY2Iys;2_kb+Hw*inSG zrjD#6Er?3uOdQ#_!mW4@^6-JF6eb3-&FZz&Uawyk>!qWNn7z$ce$HS=E>vaJFZ(Xe z@(N8Skp&cdD)jWM*$W$I%d72@jap+(2`L9?$sCXakpc9V-f=9+RUO zkU?T_F>?pcM1=sQr%F6fn5&5bWJ#EDJ`&lJERb5Aw;0qarJlir)x&2AsT`56ljIb; z9KmGHSzs;snEf+y&U5K_N;rcRkO8q16QMD%3CTb-(sNd#nL=Ai4SbuIP0okF9_0cu zmgof)flMQeh=j=lf!LXPMbC7kJ&I8+!*g~nF-3yGGB6RGc>@FFh^2Dnkaj`196$-aAql-V zCHI4Pq0%usA@9j^A)!3??I^X0g%B7FFxy0MA{K;uF8a_9!4-(5>5<{7*%)+@Jc)tX z8G?~BV)d!j+=kyTkkTqILs%9BbGgEGbR$ivyGgCIlS(esy zIfvHDB%PsE0fCr?zSyWj%UE)^$~KGaS!;LJQ9ZY+jizzv(z5DuPpt+UnL3ELKtZ4e zRREBP+|q=7DI9W_awjU|?dy$Yr06&?WvBgu#yv1Y4biYNTc~xG7nF|)^JKY$GMO_G zlwP(}wsK}NWotDrvgHI#+K{%aDIk}W;38>5I!ke>Il-3D1q4h&(2OqGEMQe}GH1#r zF0pk)iPRGpFouv&Y>&_Ob=4c_x=C)u>Lf!~Mly8T%-gnI*@4*u5_Y}ykV+IqqOITt zCE>K!KYDhr`10>oRk5mHZ`0cFY_>k&^7gnlD9cS^Fj8UcY2fJ5$^46t&bCIy>(@s| z^V2t)-R;YRJL{L1ad{dJ!qwvX&Ty3uI(jrYC?{VT7wziO>uny_9~{)11OM9db#c|L zjkJAi2k&6z_|{n7+-?5Jlf{L8`Kj?}d4#uiV>#v*cKjmEcEd@B!tV`ZH$T33eD!vH zaeZ^6=P!<-0~-{2_=tY+7N5oGd~jS&tNdtO@M`Dk^yDyp_YYz}>-suQj;8*2!6)15AFICpaP?Pya|zLx3I8hi^af)*T;j$ z)%2g+zP5wQ?d32JXH~xa89aA~bEh*?SJHNgC(roZcU_G0(ZwkpmG6DIH^}afe!O^g zMSe4*zvVvvdikBx=CA(NX- z)FkG(8t=^BtK=WO<6ngPcV7vUUjDsj@hbSjnn~cv{rW?zSsW4FRpG? z^UoKjkUbKkxx(f?LApJ z>W!&Br&g43AkEZeGUXx5tjp6j9-d9kj*lOo9vn;#+6N1~+b-JWQdX^N7Jk8z!z#Gs zm_u(KE*}+T!Mwq_o7?f}y9f8)oxKBO$^|tf35HBR-uI>Rm`Riw!mN;Nd`=@4F$v6$ z#Xtn6o|Cc5*{u2hYjg<(k+BeyiNGo2yh=>Yfms6gxp%?${jeOACAq9V&d=t1&t^VL z6}-=75v!P2lRRrr#2?f}lSG$~{obR&gQjXbW?q=gku+tFU{-RrAUlO7K$uH%eKI8^ zB30t#Mr?gnL1uCUCSx)3Og-^^?^9K^wK-U!@WcctI~MfBZwQZ#iyS2<%cRN)+&Py6 zYtr135i-Sj&W*7;Mv$^}{nCR=A|5;GIjbOR;68~b?L-$I4&r`1UxykLnvD`NVdgBl zJahS64C{vCD#ww~LMO;4U?wTqm;koUtj#&^X<-AY_^oP<~G7OV{RF!B^i)XXJ} zog+^~tl-XxdhS4pLf}FO9H=Z&G(-y{HE5pXbdlVwZL7J!;4 zAqbb;hu=_FWoIcK=WaJDWd!7ZY9knFSuKx zRlu^KanuG7!J82T=io9TqC#8IASSQ_ExktHF9IyLCUQx?Vtkt-imb@2^d7GTLHz?<+GC6YGIon~+}X9_0o~up_pY}u2I#na z_gT68p1g1g7k(matd}Ep+iU4`J5CBqVE5MI@Ea$S0m^Ir(PQQxb?z#<&zANF%i|^Q=4$QOdWFVPAEo?$mp=LfY=5)Yjt6mz zE-z5e!|Ax$y>xonkXC~?x~gvZ^$MHUW$O-y5sj>w`NeU?2W6PQyO@9Tw7iJwGrj(o zrTE2o{Bg8@)s6C;?jJvXou2+eas7+BHHBA0AhZ-1hKj zCgowXy-Bj=hsjhy+cv|AuCt$Nm3ZdTok3->CDc9i7aszkvCl=?}hEzxa3S@KV|TOY%a6`jkc+SZolZgGe22Pk4}!C9vr_rIap2)^1Jit50_b*G|c3o zf%7WJ%vB_=WI7^#jLr3VynfmpKYZuG`|r<=G7D!W9kbHWmQSvhh+T4-bW1bRTOAPmXCVuDt&QtOynrWLzRd=gQng{8S>pS4N| zKo2OhIRzqjC4~rqz!hv{M4hF~#%5$kf<4g+G~}*xt`W~no@9ggl7+6* zWg=5#Q!_I_1w^iIwgK&#`i!ArgxVyh0D_5G$yACfa$9QaL|HhAYsDoZfS3p*&?V(2 zMeA}8))h!4Gl@o`1U|pnG%_RGMSjQ&VIPQl=*Czo#6TYA&1n~Nic!$9^GqcZ861Q_ znMj#ULLaOg*v+exB$oBX@0Tx<*s9XbGITWoaBc zrXa-3g0PwMWi3H@DRGyZ2LvzF^QlSNwnj#5y_i?4)U};uWuK)o5r&x~ZAHoDmDd%( zDwdfONoN#|y`q4D02T(s_{U^7pIu0;$dN6U(Qg4aJBAF1j}H^E}#s-^{ar8b==`n zZU&JlWlFQ<*^l-Q*7E2DxxQ-KgUyqRFZQplZS`9}E@0~&mu)wb`KfOQleO)m#|Q4~ z_o@+fw=c%Y!hQ2cvPtD9Zjblt?t6N6E!00__x7XqZ~g1# zZIplH#&|N5UtQ#nTnMjk^?Yzr=^}O~XYF*xZsrf4p1gg$SC;)gUx0M9J<)y{+BIDr zVe|3I!tNV2xrN?YwoAun#aeIL+c?`CZY=1+POq&-v-w%~)hCOhF_|Gpmt9q~euN^+ zGSJA3wQRRv>_)fBxt0T^=Qml`tqytF=!3bb(;KCI`)ld%{A#>Y_J8&#cG{AD zxI15?aC6u{!s&0Uc5ynmetM}s;18a~?ZDT!z2Bf>NKIT!o9X^R*nLp9$EWYv-e|jj zdwoYV7CSS(DDz1HchbAQH+xY0?yn85ckZV@RrWqEwxtAHh^=OD2Xue`_@7UoZHz`g zwRUATZ{C?7S3^H4>Iq?7*yPY1JS-EMjqB6Jw*U6L+6I5>s?(cl>+#tG{WssiI^)yV z=*b86YhS?Sf`0xd`4Q4T_=DzFTmO4^Hy;gW|I9yy^y|fitx$jVwaVR=BQw`g&+)vb zDRG;~mBh#08*jCP)~yXfU86Um#flFPIWJN>i+lUi#s10B{_*M2@#)F_-pPJS%j4cE z4Kq1TDo7q@PESObt3WPd$!0sJvT^w4yq%7ElqUe>XVok_tI+KHk_5NP*%m?dBi6o-Tnc0X-q0;-h@PmTP6j=%? zn1~5RXr0TRiA0zyC#BTj0fgBF@!nK4iLqqPXKK?FIgw~KQ$k8+(G?kS4Y%8XjFiEJ~`9hMCc0Qp_S|n8D z2Lx6#12Q4e%G?a7nK#%OIFki~|FJV8Yc?eVlaZNF5Wm6o$N_TR+6IxbQPwm`SSfks z(zG?35E2`Rf}v-|*^pC*G^5mdvC_;KW~ETib*wEZkz`2GlVZ)0hp?d_^T@Lt_fw3y zAL*}xh#XM=T;8x}_r*fKZ+Oin;f z$UAi)Hr66osiLqtdrBeu4xv%c6hs9ESk8{LE85ntwAC~ROXfly`5=J`k-||n zi@7`0=Dwj3SNIlmR8U#Ffd$J=OG|j`ytR@#={&M0C>t}AnVEJ@XF((Df-IOTEN~|3 z462UQ21aWHW`dYE_C_Qg-s+%(U@$R7klUB8FttuwuWiz%%>pHoHfvH#nlrL6ymQ`D z$rf7TmNO8cUM(0B*-B_>xrGpEfU$y-mP!LKo06e#G=dk(#O!bmQ{{qu2?_`^vJ=*d z@(4OK-LNF21a(;?%eYPJ4b&_V70_BRQ{J1znr1^zo=}zy1fgE3+D9=?@uJZ{Gi67J zL}A|#-|uy@R+jyXn;3pHjMnPVgpCkSH@j47G;U9uCx>T?EiAA1SNClA-DPos@@w_* zhgAQMQE$uqXFasJFk7Lvw}$cg6Wn>!^EN1o;>LAseZ~!Yr5^jT*LBxcyW5zT4v%Kj zcMspcf#!3)@P@qdudsEOPX8G9-^t_ON+-|b$zZ7tg~D^sKP87PFOq<9=GcIlT+4uMJMt{cM!scET`Sc zH_q0t!#+0{{#yUye;$UPvhm-rwJoJaH`p3aPY+h#emHq)%HwYM{(3Rl%s1y4&dMi+ zo?`Z_jMI^=Kg8;7Il2!2huvsiT>KH+Rdt`$asy|M>+f*yi+p@7-~WmB@)zjZFZtO| z;_=_G7ym%+{-2B4-R?*K-sE+UejMCe_-e1a^&0qp?k}3T_mBl?U^C>!Dp^UrO}X(F zZGMSu!2X6_9$~h{>B!=JYo{l>lihp!dwcu)tE2tHv%|BK1&&kQ%kgq-9p_FEiCdNv zcE^kWU)u(&mYO#nzA<{|&C@fSWXnw17@5oj0003T5t#u77^xAA0Wi#oIC&4W=P?gE2hkxm|IIL%RQ!{IIt=w?ho9c>=nI%?@2SGWfyW9 zSzAyrc0J)W^3C3A@?qe;htn#eigtg0hh+gEPFTYC;Bq zMNAxpN_;65Se4JuQ3F*ZaANH!Pb|)~lazf_B4ue4a)+!&DOYXRMAZaj&htX2fdX?) zCNO8&NE1=YmQ`VyG{dsOk{OYbGZP_47GO{@8WL}b2y!%CC{NXbQcp$I6k;$%xu87p zb6*ra=Z7R?a8`*_2Uqrd#1}vL)GqxjcK@=cJN@_jRp&2DM6}SSvUNhTKIgf`uvQXZKK0u-@MG_GS z!3}&E7DGkKmYu0HCNhF-o4aLwY3EG?XG%l>3z$vW)Dn<*1zC^}Vx>biJO84ck~a%x z+^Mx{Ia?HqDXOHLkdZ89OCh1O9M}rVFwLr@IlzKJSm)j*jtk}<2X^o*g`?oPv)tr# zKhh67E(+f67bUr|k`c|UASF*Z2nBA0f}@&PNr;8Wi^0q^s8#vA02vHqkeIB^AZ^9m zI`TFMLh31W7FGn95f5@)ONLG@qmbt=UK49PRvze*EVZEICFgTn70igp0YM6pzKOJk z=6o_u&Ok;)TM~{T=gHDNN#N6&hb#KMVQ)$09B~Rgm1b_KK^ODfVtYpI5z(@@ zu4~nu{@|se8ihf>SELY^n0MX7cGZ9j7fwGo`{EDxH;aC+zQD_+D-T8MyuV>ptY2DM z^s8@`b+dY5t9kPz{?dchE$)44XJ>$-TVUAtJL{D${Az+gtD5F#kKOSP%4Ku796ufu zy$TDGU`_id z`dQX?+VvN^UK(FmTf0$Lll2QvqIG|8xad_TC2~t$C$96o)N^PbCh`_-OS2_J99| z-pR$E{Z#z}O#a$8_g+eaKXK)fy6$avx;^wmsU!k@*c$x6mN7k?rjf|Nc_(~r$K`cB z&S|;iJa>z!CDVi1?#bT$XNUXm9PTfshttJzlhR5!Q_Q(d6X{O96Rs+jF_o8Cde)jH zRaky7fBT2~-@`%Tc|{SFGo6d*0RRS)AD1}+`?nDv2BMDsUaBU8Q`0UJj4~)vc8Od>X30!M!h)W- zp`fL>U^PonO7P0rGYZnEsZ*=ERCX-lofkn9P*94w>!x{mOp=+|SsCKk`vHfF$OFMF zlSVL9Sk*Ko)odEU3!;SsiFvjZxGgQ2W@QzhowcS(DQd_HZ^W7nnK?U!kRoF=%k6E5pjj{G9angge3^|>vA+SPMRA{WJ|N9p@ZcNQ_4gv z=s9)1S)|mZMAm^Q8FfmXQH18G)?&##6R^18DH18`5;}znC+RXa0EKs1<9vUzHr$&v#(76u6j0VrXXSTl^kjDh2RP*Z3IDU%T^M009W+bE|l zbP?KQGR_DolV&z{#>{YlGgH&7Y5;+ai|jeNsG>c#ib2doTzcOta?08!OI?!2Z}vie zY(ZTwfk0$dPza7aivxiLgd`G$#ZxF%O_Da6V;7ky1eRb_nIc0|)9T6G$blTu%5fV= zmu7R#1eMOEhB|9e%0R?U7b^$`tDToVa|;rlb9-DeiZSnX{~L-(!K2Z zua{T)=vO>m#=Wz4++}U(1DCakul$g=zuMy-%|?s$68(#P+e7!Gg}j9FPgeTPZuh^L zlv|j7tY;70b3c?DJ5~8oab0Gk`g7l6s~;COn0Fz52aA2hQspB7`3FAS-1I9Ls9 z#M{O3*(ARCth|cZAMV+2(dGXFm!FsNFBIFU+J49Lx|J_jue1lSxQpdI{@+mGp2&rqoxjej9V6e6R;c1u|}_)^pl?LKkb8 zJ09%Lr|%xWae)^vmY?|zto=WVhtCyHKd0CBaP8fq9ou`?+E2`|zU-Z~{c7KD+`;Cb zpqtjhfwsVt%PZutV{2X zf8oITZ`#@o#b>Mxv|KXnrp5i{!A^emI&Qo{xBjKqkGkEzj5}Y)&0mz|r@Dv#$^4~n z;<s<|pKdG-c?6`6uMb+?5KQFH3dEcx=e@eZ5DIB{1-BEhk%1omYRU2wq{K{Mr;vo?uU zm0%-H8#z->rkx{Y&cUoA=L!zNSAa7F)5>%NtgFjyYEoQ)&OjZR5}9KP%!~k*u1Slg zi`h&dM4mfElQ=>nou6m&jB!fvWF-|!3dYp(u_Z|sq8@|xsdGdn5kMYPYpzlfGb75N z%-#!C;(EkAa8~V9W2R*eakb2g+~nMt#FSdits$SYNkrC}uQC;e40z`qj|B}`+ji)p zT7*W9M2R_*b_7xGo3bd z&o6tunzPbrauxwnLNw~M4A$1FDYl(1qq@diAgL)-rUO$=nv%^q919%8gInT+!cqq$ z4atI%c=H<1#h`NM#wBQ%G(}a(u@oiB#+HmD8nL+}EgknvL7jG|md?-tOK%2d6DIS{ zSj;oB8D-?9!z^Tgyv9c3GP9^UWhjxdDw`UWjGk4|)vHF)C1PkWvlVQPWU8DakUXgr z=4u(R?-{aKlrC~=JayBgPkc2ZYT{*~YV;Iq8`XU2Etv;6zb>D}$#D>t^oM$z9vagEom%2Bi* zoq#IY+LXzt{eyCTi?%5Ewk!im^*xBlAbGvh6acv;#>O0FxlyCmNueAKbuZ^np;+fgSny#-?94LHVJmmM^ zka>6P#?L0H{Km84(276$+TaNK%U@sI;o|4+4t{9u|6y-(BldoNyye&Y$(n9o!j)~B z&TMzz8YEw(y>Gei|4P_~|KmT=`=Ijw)th!h+~;2^9~@8r%Wv;*asSg>7hH%+PGj6pnwDluQ<6-}NYm_!EQPS^J6Ae!AyA=DPVx*=EA6PatE?kT%gd zu@6Ri{yL*XfUp)s1(9R+?9005IxStE_~qH`u${8H(1oBw8N#KI156DCLX~#~A z+VeRWWW;XBtVS^_GN~F`wPj9}lCufh2}7X_%OdJ{Wwcb7BMW(vZ5h!fjVUFgj7$n- zTdB2x6DuSqN)2NXig_@YNmem9GtPvRCP^p2A@IyPM?7S*!sSXkpvxUC7Bb0mO=n`O zP^?OuBt7fY2%ZTfCPdIeF{D@%mCQY75$KG1(drIH-m$BQg#{!GNPn}8t7)R}dvZWfDaP8_v}P^Rq2 zlLaycgW?GTnn#oGfOpjq? z$pdU)gAt5$Woe$fplzkul-QWf5yTM_sTyUoRYZ)wb7c-v5qYozrje|JXcOYf_kthw z&Kuq!kvo@nU@@9@61_B)&%~J=8+l+Ra!Mc~ujryJb9a{XG@Cc;Su|!7afYCdyO!FV ztm`PvfC-Y%T4!BGw`jfdQq)4kVq!_DJ8#G3x{E#S5>rQX4)U2>njukEpQDG?FbDGH zdWmZFF{P$!8{-AB8Z)mj(1y->Zm?aAn3gWg;6ipBoC{?LWCM0<-mm%ej27Qto^D8U zw^yBZbkg15^3B!a<{8ST^ysSF`xM`r+txc_vSSZ#&u=%Kc5-inANJw}!nKVa>i%d^ zj7M-cIkD3jZ+2Zmb0+$>qPmxN-l4q>e|!}eGuB;oxLw##Uu?0pD$U}z?c{|%{Lb13 zXQTQ9|H2*X{R#G$xnkPrb<2&_GIVdnxOryFW4^aWbb~HFv7H~_q)+c$!R2GS^&!$W zz4;Nm{`>g&|Fga@UhA$^w;SA6fAL0HyzHwT)&lZ`5o;1^J;g?9X}sG(Zgt5?cA@nzT>wqS^k8! z18N}Ud&|e)Kl^YDx>b+9C>Q@La^+$9sw~;YJ^M3#I6)Ggp(;Qp9B1c|F;SM|*qI!w1uY`RPeM zJvvz6>58mvIcpY8lCo4LUc^<-SB|}7YFwK72Or-1@V#e?NnC2zF&T&n!oYd-d>%3r zzzj2(!GQC<00P7y1~!m_l+HIHtWRJfiqIvjHB&QIbB0LLoG%RXkMlSqGh{&yrUh9b z4va#;Rgo%8A|0Cu7*pV|w z8Z2p-a+67oyz{OuiUDy=fcPkGfjqNLAi<;`!k#!#7&sbuiO4Nd z1`@Fmn{e_L#ZH_%2*j|$Ou=fhRGXWbsbyOwYm{Rb>S}03(fOisL0B>g5!^YKk*M>u3dSIBUWqbsQtK2+Gt9E) zr0q=kj7>aOE)tx9RI}wQfV0B9pE9NC-GrZwaW(6fzvtgRZFYxin^5OlXdc9}kb#L9yxWhs5 z&JU`MOwaAmLE>LIvfEsI{^H>6!^PkI>i+GSdv(~~J&ga|Tk{uU@AK>1Whe@VV#I@y zn@xE409rR&JIgoUr+@lozUAp>UJS<`zjG8XNcA(@>x-=4IGS(P)lXht`X| ze!N-x+2WQ?{Clf(ahq@MIAn?$Ew8f7e{etk;&+n;x_Hg41x|_A6(1YYI}!iri9J`+ zpSnulyH9`X3%DiKFZ`j6c(Z!MxU}J)zg6)CUkxED%Xr&$Z$LrNk z-s*k0TK($&@x}GEpTBl{o(?DYpa9R$TapM_U$eJj`!~M2e7URs^vml{hVj2Xo;>HO7sqR*_w^S0 z>ndw$c`DDI`n}V$XL?Xj9cJt7=<-WCh5hEY^eXYsz3Sh?{J(wc^!l>;bK4tFr|qx4 zJ-IR~KDssxcc{FaSJ`s575JITdNgW(43LRSd$xiT9*Dkks5iE}b^PT*J_wVoF~l(R5C`q9yQZ|%R?K@-&sFfvsveSrZP z0L%zrfWZc*41+0w3_>F0g+OM;!187Y15V-&`&K5)O4JY^2uD1QS(b(?C{c^_@bQkY}rSv=C!*0<)|Jm^dgKtL3F) zrAgwvY#Peb&e$s#c248sC51*5nhT{-7sE<2m?$U9ktj$JLP*k6;tJYR87QxV`b0tm z2*JEqIRAk$sdq8tBei2yM-p5xSCN3(3uR^k8?k`l0#|{&ft5YEk_<*!6HH;`l`Caq zF)x)1fkS{VVVX52n^|*g63TMir}L?KS-4QBW==7hwxYbnVcSV|y-q@1_ItIO=A0>! zWAnqrMR%?W$a7nrfz21bRG66(T5^pScEqVLEh%O@&7e8t7-R%T5u$L^sm07>!irVY zStL(dOA_|PVhJ&FhiouWX5vgaI6n$S9XOPOvQCtv#*Qo*Kvh8l5v~T{o{2MKGG=n- z3QgRe`jymRPiC6W|1Al6etBFH^%xK`kMqkMCNa*Av;eX52*Tt%XjFzljbw=GATM0q zVKx>x;vi0>RIZFFpn?c-fD@RJIZGb7FzpdaVdr>S@^LM8&Q)`e0_;WDj~s1#5l}SG z22Us)dq}U#!)!^lBPMU;e9zE>Wai|8Sqr7?%$YJCiUvv=VKwDFs{zk~Lp zZtZAx?cQO1k)FO*Y<{h|^-ts0px?bPxcqjw`FrKsdR4sW2hNnujj$R~I+z|lKAh~> z;X5#2}|_Lbjzu$;d!$(!iEQf?kGznAB?aP)C5_S=j1y0dHP zdmov<3RJS4&er{hRqi&gHWzyzyZp+}jz4ihyRAI+c0}^(J-PbbV)uGKy&683aV7i8 z;c@DQgj5#y6y37DyNTh)%FUbAdgVt$n_sfrreFXZ?dmVZH>`)YUV7u){J{N7*Wj~wG8-wIo!P+jKDEyZ(Mui4xaKV2NYe{!g> z{($FAQOer@Kdc^NGeYcW>VPXysh)Hnpq4 z6|o}|CP(I|(jw#<%Qj4=#rXakJMa8p|InT_nY_RW0AlVtj2&m7H8$X!kOZ((4(3cr zRlo#h&m4&DoR14K13+Yr5oM|~C^636LqG;7F(nZ=As3Qx63=9gk+OA>m05)g@jWM6 z)v4^4X)?@b3+bPDU+$d@-X0tt96x9$sv*QeiOIP>FfeiiVJ?_@0yT7SYShA-K-qbt zb9%fsoEJ(fzX1@|zvI7yZ)Irv3VMbIZntf(K)zqSKb`Ip3&$WjE zS^&Y&Q|V_^mQ*RK3xtZzrzl+_5VJdCdg7h;R14H7sXEibR4pg9mF0$VX)>hTNih{E zh-)}wHp0qL8+iZV$oSTA7Gp}=xbmEp+uUv5UYdboVuhiz@*AHP;Y=SdlB(f3GIO* zp7Y$uZ%dXc$+eeYRE+t|^0bdvhoW&B` znbJ{a$J!%UCN**93i9j>%oV7npg^W{4BmxE)~Y+VV))ou?u0>}!i;b#QafFf1yKR6 zB4|lsj-4o*zy?9{P^s#&Tc%P|ZC+zc+FFK1I$v_`N88TMo##eOBwQ-hT6^Py*?Urf zG5=cAq8hyzty6-$bn`k}>%zPM#EYRa zH-HKhgb0bGE3!FdLKYYkgjrm0j&jZ1=OO$+RSL4&5=*CO0!<5T6skYQbsIP(>$0v) zk2#(?N-jglft_g-QJ-3sWln$zEE{z&B{HBvZa{76+rGw}b2O9lZVH1zO-VCqye}L$1QEQBp&gl5y@Ox)ZB^Jxc+HkqNbm7chENAUt zP|C;dxLGIn-zVnPD_2(U9ms$8HMy(RAA5bnZj=u^Mq|EmN#=y#-aGxS9>vr-+u#JmHX^V)g$eG?QB+t;>F%@ zHfg`IH{EdM#lhOB^6MoQ3!9wS*#lm^BTwhM@6`7y3av=KP-)m4{e@2!xf zz5S`?N#cL-6?&DzAAPCkuW=mbD4IR&7wSQ-v0K{S0{_^ z6jgy(ca5kQF3cN?sk&%#NB{C2(Lx@S8fC4eGb}X6Ak#mATE`W_0 zLbXW2l2Nv%EAt&iDA=g55X?kbaw3I6BM}&tv8b+;S|sk0Ycx^roKze}LLeieilr2c zE59)W=ZvmNIV}^z#F;L#PSk|B5@KV48gd46=8&j{^cbz`A`_8k(Pg)qw4yBHkUUGx z8|>DNoHA!~2DYq9ssKS)3Mv9QNFmPkpasqwQHTZS3S`Dc!k$H$m57Xqm`8H> zCNSwaN;R-Xd4r&TkP%@4%0+U&jxXG$s zw6rd@xyu>IP-N0<9kB_)kp^uIGthi1uF^iZ9yyGn?x3~gxW?hk+F^N&%rQiGwUrWt946;3&2bjhs0*&f);eqxZm~bq)d$?6s_I^!T^29Xl$PzP%&NA4 zH^Zn#yk0)2ru)saVGqlT>ksbt`(NYn^E~>60wJVhDsQ9yEK5J1wrNmjH?p&{#r-Fz zS0)&)29pV|?Jh1{Ye{G z)E~$8e~ddl^bTle$L!_2D(!wty(6CP@~_9$1DxG$u&q5er{&(Nx(xb6Q6Dtj_s(WJ z*nPde`1{2x|F}22I^>@!M%%c2qqux?uyL)vF>a31nU(1J1^;F$f4Oa61^-$1+#9&~ zD>{9Np8h*@;Vc!8^kFZwYyR#Yu6*El5Xx=2RAO2XPO;phC+{A;@qs(nO# zsiF3i8{X$ljjfQ#l#o^i7{k5ax*?m36s9z6?PGruWgB4^jToEkiRe5eEQr_5wlY(@)HGRAbtZ){3ZjXo<7`!+QBBMy;7sV4 z3!&Ock1-&cL(jk*h>KzZIwlfNr6WgF!)+MWNZ2}9mh+)1v9c4QKoSU!MO;z%qM$73 zkO`e+Vxzz*Q3jhbKtga}urs%2vZh0C^wcM@#r$(8;Icm;oqA5U0GbgJu6-H3X=RV*F@l=-R zvurXd=gFySK}&-gYiFqwrr?_1yj?pK zsi_@E$-%oAxh;xbzc(lc+V?gtisJ^)7HLbTmtk|9K8j&fzH)Q)Wcln@PVQaiwU^v& z-|(?(uWpwgsfMRBdep{I!PiNio~3V3y29COrG4*%(|`D*!#lmf=Wbl~ezC+sx!J$I zxpPXh52}4HVf}b8YYz6eAGxyMp6qN>xPDvKUg7aJa(fesg=)uHIUl45;X@hD~!etM?Bd*FX5(%?s4~b4wLYD9aPmY~g&-u~gEzy0O6U)F;s;U;JwXJl6`L?c z@&*I3oI(AZF!Tq(I;AEHsL!0GgRHbA%!MqluY<39p*F=iS|y=F%MN0F(t^QU zI;UQ^@UdbI;__gWhtjuV$EX$}GPX>rF}1VQ%`HZdnmTZgP_ogvfLDY)RZd31f`gHQ zSqM}X=!+vR6_mufs{v*kp|oT4B&X0Skn%hJ~lY#=rc#?%rmRF%Dm8DQ3GYR|PP z07XLtBaFztbd%zX|DM7Us78nw`d z4Mny>GqD*3quP`bC~6_>3(kZ{x+Gc%$`Zp8o0>02qpV#5B~=*v?Wa)!*-1kMOH|x6eaE{lgx8*KA zv+j{gmz>>@_L0&Ln*AF-e%#+Z!Oqh@x%E+~UvQ|_B$RFyh1!|3lZJ5Ac{y8jlj(y( zR=uixLtgx5QQs^lpTuoK9m^-1aUnK?X09m zADzrE_8?dLyT4mq_$PAV6Da><8gHQKs9za|XoqQct-ElH@uO-cZsod{w@`ny$Sn0^ zvlr0*aoL{E=ifP+)R61d)%WD;S9tSf<3Db7sjE(RvMpM?U8V=)?qk1$JHIlpo5RKH z{9?iYVG?vtR~L-LRlAd-KID%(Y!78qIJdCeJ@!rIXeI8QNAu-dN4GZNULFj8quBVn zn0}h}|EgRxtB?GTe0pqWJ0sM0$F+^ePs-jk_@AZm7O27am{0fl2ef)4oW1^=eC?kN z=eP0TGqWomb+2w8W_)|jcMyN3*ZUHMzqjl@0sUWBS7yb<@A}~tvX2^^opxx{EE#8r zk>95NW!^3+6tphX7|nJ1SP$==o*kafC#SR1z4`pfO!u3F9Hq^sErSfEj(o{gL2%yu zW_H}TCTo?_mErFG_6LW%CzE^KBojGxme0N00&*Y*aUd#KqFDSf#{wXLm<8+%=gxWo z*ocS(#2}JNY#@}Cy7OKL2_R$;ZJl-lb3^i(xMMK}NNeT3NwzL)lGKKj`rZ%9-0Kgk z!C>8JXI1Kx-S)m6pUfV2dzq?IdljXUT5d@?4$R((5Q)$t=$RwOsE8aW5DD0ka2Dzc zE`@tl02L<5VrQ09N*#g0&cPU@XY3By8?$34!s5}uR*2##2s;)pX+<`3>g!MzFj3`f z%wQ}{XV%svW9ko~V=yQiM{zy}GgVHenzDwdtChy6&QP-F5WFKJxEi#^t}KW>SyIc% zK>{2ZSr#*7@~Y+x29Cx~wIZpdC_Ht9#w5Ai1Z_e8Kc@aP#@a2r@5BCUt-bd%oO5cf zdguGz@xAwU_vP)8Q+J!BNJgTlF}46jiQ+hrksvlK1WI5#NFXFYfEZFV+mRxLkvzn< zVv!<6aWb1jlj@1;`A&WN4(~kFTxWcqXYaLEKAe|Ez7*Na}QLLzEWd|80=in@WJJ|x5IRsP@pTm#Iwi8{dBHYP50ukzP6JG|J zlO&Hd#RRGyMkq{CLKD_w4Mg@PdMGXf1e7>N5|X}_W}HU5O}bKTMzR7XP(<1w8$0#= zu}i0Yf>dUrCAuY`;?l$%%~*RivVP-I*F9H+ z$a@AcAz)TlhohW@*@HN>7HaVjiLz7wG%2{ndLV9IR@#YsFu>hyRA(I-xw{e}22}1a z_$q)aP-G_#>pgm8u6Pr9KruI{@j}bF_QF&#yPJFVTxcL5IHwFM;DAtb;+nLH&TxXt z^H&I#0Vb~mO@s$hDdb@9Q51Q|R5}K65cU zwd5nFrQ(7EaSSXWcxC-^s!!sgtu5&F~DE6Jz7t+S# zSu#vMedWq_(*mGM>Tw(f>8B`qTJe*696vx~K5fJKcv*LkH#WTf-1W)PHhybZU6JO; z_qOkR*!|M)o;>TL&t5)wczW^6M-N`OwDA*HZ;+&Z!>h|S8QF44cP@BkOAphtx8nD{ zP;a_?`jhFD?Cakiwp@Pjg>=^N?@wtj$&X!4_vfeInm*XOy79`!^-Zt(8#s6!S1+N! zN^pr5qdj;i-+m*SEq>tY`J+Dm=J%WHo__Y$_%`c*dwX#c^`{TE-frgquk_^EIQ|L0 z>_xi$y!SFqCcGNxJ|S-o~Fmzu-lovm4XWh}n$rPYfS z?sT+ST-5@R1a2m@)pAy5mUZLLs;PO^bCmsCKJy_UMP|zA~;lyt!E{n5yU&@UZhYgMDAQ%2kYGm%ACrE z)Ehk7u*pW=AYCD!zd5`8-s;_%4klII*r>`L8B9F%)DM!PG!;vTl9WP;Of7N66iMRR zcf{xhJzEKlByD=_G*T^I1}}>|ocHb_ND7P?S)BRlrj{v&S`t-|4NwxIP*pOng+f?W zbzN6mAt<Ti+Bozl25+Q;k7%HO33PK`G62+Q&lR;6y*#QxWRLF`~6~#p49@MR{KtdfR zZ5(pnua*=eRTXg$?i>sb(PXZ+PjtEa;WGLVyRL3A9-O>VtAOOjiqCmj293$3X%p2*;`#?~ON= z+=uKJN=L;kkyPe`@d`mgiV0G3(xqBgU}k2fwsJK#kXvD~5L4AQwX5@>VsHa`&P!rP z5Vwx=l!l=cSMMFYo2i9Dji44%i-Tp&a~)FQlmiq2L^aD;APrFqCo(6K7cb38nNSlN zrk0`5jLd3c12m)b9bri>fn%hl`J&jac2~6=S-x+D3&lQCF)`XS-*cX$oH0otVF`{3 zp>~Wx)u3Tv!{Bq1Q>N3f{(mC|G7r$ggjKYeaPMZSx%blZmdhR@8Q`1(C#H8^e5Z=c3;Wk6voFerLY> zPp0kV=J4a~r6XKE`M^EO`kElCIV`ycCGOn6q>-dbA4tNYme0p1#mU+{)!r0%T6 zgNf{Y2OGc3cP_`1PsnQzapR4M?R0dF_G{Rt+&x@=@b>8@VP|)vZ6}vvHM#2XHNzH` znQqL=Y?i+==A)f>^%wNTzm<1>C|vvp@#Ej{&40*`K8m|PJG`2)aZx{vl_&Z5IrdIC z?vh?X7~_0GS1+_b)JHkhIbG@T996Ycr={F@3F(ie`q7E~>h0yTc>Lqz8~+lw{!T46 z#yD(W$K}`g+6sDv(P%i`>)&O+BlOyD;1mC_k{^a+gYA3R4bRLR1MTpsYDDbVDWhQ@XmB~ z`{?ew$KQH#Iz4~v^VP628O`&hMSPH_Z`vxwdJb8dq>^- zi^HSi54zKpun4=8t!+fa|9_ATc6W0xfUo;sA{e2pCBO_kCHei~*L8yogBOQ81d<3f z$lw{qmZdBJH>4iORAu%OL=gsJXAXg>*`~<`R~6GF_p5%E^6fYJ@Bi!78JE&YkS5d> zBL?9_2+|XG5}Jr{AR%Ys0Mz6aNg$5kNO1z%LOcUM6iOAf>?5U#(*WGD2?vDeQPiVyq(JP7K4WDDwvqIG zSfo|o&3yo|NI8cbd7xr3uPv?iSaHW)+#51yAxcrTHexH}!)f-%gN2Hlk&&uoEuOP+ za1Y2^HtgxJ=ylqa1XLi(h7y>Fh(St(07Nn(0g*8ZG-#fv#=@}3Q6$9y)Om6;V>=Z; z3EIYXE5=Bn@fgXR`-;N`OU2CcbnF2lqGUu$2F<4ZqF|Ys2tk~Kg$VJ$Rs}1`5yd%{ zR4^V8U5cl^*PJ=KW`d+x*RepBJPzJcVrg_N0wAcs*;*zjcwtrA}KJ0$Q-Uv7ej9}*LJDC1Xkb`hag@v zZ!&hlxyaJmmCM5I%=D421hffZ1FMa#Y%2|&O|u=k-X|47L&l2WnnG3c?l1~V2{UnFRO~at3CzudTkq(>0o1V6fuqns=t$Jr zP~{3kO)w87jNCT?Vh52GFB)84ytYZ+5^7>68JG&}>2bcJ6fMd`R8bg59#v9`=d4j@ zYII_GVVruC6~RE{55iRb^pckimhoeSBC2I`$3HLK@!~U(Par|I(9#YojI{9HjWdO^=(d z*{3VJH?st(>M*MKnU+^^Sn+D;WOW*s0~q-5$@KP<(}&CBoKNphAHREapL-*XB`^DK zIbGRNqPP>AajZS?5ST=gRI%Pot;*Sy9^Zfb!NYrRA1}LOO+u2ewkW{Y&C!~e40Z@r zPK}cjD^nm&1QfQi?ts@O;PqY7`g?~nVC1%T6*`j>DUjXG9i?CZ`HDawX3im`m^{)r zvQkK%Ml#t;JJeH{<#U=nsE_Y;@4bKVR+}|ZD-tLL5eX7qLSO*|6sZoBn4Aa<0uv%r zU}6wE!9WFANYKbFqgsRT$XCQ!NCQ!-oAD@(dE{|J)!o!B009{`bF}~n`H)y^HV=d4 z%xsd1G%DJ$E=b%Xxz7sDil`(KC~8aW}6oiLK#T8xW=p}&#SZIG{%oLM&Ad5sb zt(gj9LKI2A@nGnPoZQTdxoS_tg5#*(*d9}7HqR>V z;?CJ*_(Kr6x)$#oDqgV$1g2U74P>3$qF*jnnF2|Ihk!UNOlN9vVW%1V6l5$RvRA>A ztGcOiamox4db*>f%6w#Gs;b72AQ*Aj5UW{<*umL&VLZUi6TzaQE<7QxDVdTgL5mgd zGE^;4X+^d`jT1RJ`!VOcSaqhXszz{DR2d)_cDRwQ6+~8wmR_~f;;upxIqXQf7HGsy z0wGF{AXy@^ zAsH3oEU{#iUMl9X#F4}iw^==VP>xFpOA(bI#LSVz!B*INrc7&t12I^f#AyuL;sEW` z+yk6RvwCM_>!ALiG(_fFK(szYaG@aXkxay#p{8!ZU`hf&h+FfC15^qP1R{h?B{NN_ zdhEk;10`Y1ldbAt&@4E=(eg9R)|S|I;zQ+kGn4f-4ewOpcj5_;_V*{}!X;qmF6s}P z@9k}LH{$b4zJA`4#AO_|4s7$qR34SLzd5@y!fR+D%@=&R-bqyf@zd#cK2- z{HHwzKU-L>)ERy7c=`D5#q}Ozj4p)0t4l5FXR1Eu{J|pZp?o^6zIlG}G!RJ}004jh zNklTXoqhAj$D6J@K4+XT?e{oc@|W-Dv-ejo?a}yS zZG0>6ua?h#Qk2n zmPETp)1&)`AJkaxwAC-^<-glq`84`}FKvCzxBgy_pG5wv;gfID-anR${UjfkmoKpY z81+rqr@UF($$je{Q@KM&t+&@`e_zsNT)TwDHM;Uu-~M?y+*o|`>f?{>puD_)_Wu6P zueXa=JpOrqacrA&PkX%D;*FvU=*fV?Su)oNi@5$&^&LleXHkiw!K5>;d4v|6U~ zEqd>^fA7w{i`fTyuFQdgulX8lI);PR?si(ob-8hj!AhEm61)Z(p{!qD2@pacxRcSE zEDZR%j|PGV_DD)7U<2R+g)4}K$k=nVSsW~Nebhb>P(<@2&gS(=H9I;yIq~}ZY%+a# zzG@D6gBnhWlY~a1kPEpmgaZ+YaEd_^gBS>;!lhzj5n21k)iHq8DInH_$S|S-lv!LN zIg*8>PJPAB$2(p1{sb#7~y{M@TO2q>Z znIfeTjkejhgmc&R3rU1pxZ+T88Zjm;9x9D29;Kv}=DF5X2KId7bO@&aVJL|KUX+Sb zb{Tw3VUrt_;_l95L2Y))q$(MU_awJQUFtxx*|6Daom*49aqO^KucEfEWR0STG7^6oMq>o@r$wPJvva zvh|Ka&Qfu3lK^oe^i0B%2+m|;nMxsX5M1C4II){_3ty~&fl1-osV{;tG+Ln{lZ-;z zi0rOvU@{SRkgq@U7~DEDH-fF_)`Vi*yJj-5M{uIj^RVEQycsD<<#WWH>fm59CyXl5 zakEIxB&Jw5ILhIu%M;i}pcHvau0KEi)YT0varaQSCe6R`%Fd(B@U2{~*3BQ=-?_)b z*Spirm_8Raf9u<`|I^nFKQS7=wtHo|>b{#!Zap`8b@u>$)ohU6a_su)J^R-8JdNq2 zH~77GFaGgYPF@r(yM>p2SqYwy`<0#l3Rk`{{*)@yY7pTj{+USGSwt`DRwr z1UFyA%`1)`X^++im{RK6$tC@ryjF zj^3Np-0yvAwyeUvZ&dE#2Y#@MSLA-@`z>zVAl>o;KYOAd9@#}r<6Wsh;gc{JRa4sR}TwydpN4)gcs zCqpnEMKzW_L<$^YRq6w6WZTnT=`PN0KX~%+$-DO-zO^_h^PyqFQ>Cx1QIiC6;`Q@M zaAJo6q@)9>(t0rMCAcMq5Eyj`E@*iWbqH zZ8<2l|^wc&PF_mVH^>gDot3q%80QZH)9zbB@Znu0)vyf8@me5 z3CFIv^xvlY{Y%0=-)0`>&2h)krUsHyfy%0ZaM zEH$uB!MWy=X-+f~C}1E5JNh%V3stfljY_esw6-KU1t%wF4U311(>t>eq#7q;2_j%7 zaGb`S^V&tN8CKAllp#3;W)NnKBsPS`(>`T6x>*dc;Z!K z^WH9;s3mWRgx7y^Mg&zag`5Kg7ZDMs5D;akq%>YpJ)yehMqIfl4`4$@G9sxM!MSyx zFxP@elmG#9kB>S z@-QCPn}Bf{2I?-tGFGu|nn-}dh>3;NI23Fa)EVYvcZ5Bcd!Myg9$>Cf`k#* z8&bGCwWX`n%UCWdbZro9CJ32{LChPsoidCvH&MnFDME&Zf>2Dr(Txk-JgcuXST&J4 z^H8J%$;&sNNrWnD)i6A`hB8RW;w89JadM*I5MS5SMU7P1l>mrK^xiS|kjPtdBhTPM zti)azIluv8^HQvIPQ9R1Y&AOzb6uik;Q|aK8zMWQRrOL_fTuZp=71c~o5BhVFd{h7 znwRc|H5A>vF)R?L2qV&Mp3RFNPiF^Qu7zpEywOTZmyj;kp2rrBN;%@PL9_wcV!A@r z)wbFiJ$H!e1Ag)fKl(}XY*!!Id{2&_7j%p2Wd3fDuLo|wI(+Jj{@U(jb*;UW@a%rP zw9}ZxcC&OZ=3y6ubNt?-`_|dr8$A3#b@eN_^{Y{yN$w<69r%ZrmaipT-rKk_>`dNmcPD=EGOk>4+wj&=5-%EB2=D$@Km6D8!<(=liMQUw zb6=IXAD>*K=l8)MiK~10;cw6OE+cYtVHDXjk8RKDTUXL;qiYMKK3O# z`0MelPw>0{_V6R$#4A5Py1X@t&&tNG*)x_Jo0IDi@e#T`gd4a~!9go98*ifAlRVFh zrS?Y;7mM$m951IG7Uj)Z_q7YNK99AT`|6BN$5q&kRnNSTDoCn0tCMQApK(R&2X`O5 z^Ujlp=ga%H7$R59WaQ2UvbB@`=~WRw-2l4-uH>1$(E3S+5-czxg8=t2blV-~#To2y zkOI!&wU0f3YuHm$@H##X77tK?Drt5#HDv3=r!jYntQY-qSoO1rYD2_B>_Y2Rj7>}u zg&Pin7}qGr&UtCv5^qt(B%tUDw{*dF98imIFvs92nL4sLsfJ=>*aVzCugu+hAj6Ge zF-mIaOymKP6ITM4?DJ4g(Eaot`lBvWI`$&+A z$3En~tW;|CvDT$udR|315ESm@1wH`Bu8%yo?psC=neGIHx*m;_0KrKTB$9Juv@s#w zLCIz-*D36jibs|x6`=+zOXlE#JVnu=$*4@l*D)zBfrmzBfo24Af|8>sug=}4u)tDN z%#2=;h-#1{o3n;8B)^OBevombeG*yQ-5u_B;pNoZW>{|MLW-t5u$v?EP^yk<(+D3! zHz*a@k^ont&H@Urd6U`P9YkUriPb_L8kE51A;k&}rDlD&#epMB@QdJ-~aV*;^Mg0XM~1V;Wp%D{n2E6pM1>sm zv{C^XNa4;f)l!Nb!5)a!F>Z#SM8#9=bJZ;?^--=$H*^|-QBWVM>J=ZpcNGv>n$0^7eUe;<+?o@J)rA=v z$P&;5NYY$L2N zj~VT5v%3}3l`72C^UCrW$G)O1@D}nBoxkUGXn8b}3BP!grxm?>f@|VGc-1e?-6gUMLq zPu^|bcBKXP;O=)J`+e&Ki@?N2|m_r2Nj|M=nYr*!k<+c)Y(?&t zro`8A^(DW)?Ze8?mTqo4Z1>)kpZ|(%bNbO2Mz`nv|LwcyA6+#ct#%T7HR<{yi#u_; zJnuIT!$tM@-Hl!1AAD6t&%qnZ=k(xAGO!!yjjtX5CtrAQZF}o8&p+d5`e8qP=0^3g z(PpnjFE5ruT7F|}+LhO*o1X~vLEVD3c1SO~v_3!bcOGEL^wKupdDs8(uiMK+KmLN; zmy0jz$>?(W$lmDoy!>~^WzvuS!t?~;SHDzmOZYQC(R@#a|LDupkN53oKe|78Rpytl8&j15=T)| zi9{5|V+c}J@)Rjq5h8P;x)3L|u@?>qm2I%sZgmyLbu#y?)~QXi zGIF4jEyGPv0!WAg3pL5dp`vEXXd;}1C(`VQMV2bagoK=C9J*k}OD3*GqEL*XsZ5n~ zq293c?xI|rWh1F1_@XP*MVGZFSSZG&8;0JCf%4iqpbUdLoV--dJLY2x2&qlY!DzC( zVbL9Mj)4O+RYHZ~#eM1C1I7^HI#P<3;NoN)AXKL?PCat3gR1M`+)&tLqFQY1PE6*F zQeBLRjNM>NRFOn-6ZV+4fIS));R7s}e$XWZkl>j^;dQ+S5{FzuzzXS8(7gAVyzSEr=3-Z$65 zs%f@!SBcJ*lYx!Ab4+c0$qk64$a_`Tu9OX;Tv3Q*0%e#d&rLUXTY9_@IiaGOc`#@X z>xyM{6a-};1XMxW(6>4(w(L#dkpXsRG7unvC2^<-0koEwxx9RJ9~`Vy>Xxmjhr!~Y zYgAZNtCS6%*^S89T@6Zr!;#>vOHnOew%K>Y$rM>BI4aAA-YkeF$r;`QgUBE$lNT?n z2%ecK1d5JINnq-(1#l>NX6Zv2qE86h2rK59NS8tvqY)WW0g9)!tzsQWDS&$f~P3TMYCx1SE{G}@VsYbS|8nSr>`aGS4?xZZn1R&m>>g=RDXi;Bn z^8?netu9_h{?lpmaCYg-XVp!9{OM}{3#AqY(ydGpC?_ThZzCer5{x*P1*bo!ddnGdb3b8v6vF;jbo49z@&)9dmY3eZ>%T1fRkge%mnV?Ra(JhI z>r1oUEu>dQ?Yr&n7vtnrO#Te*H-rn?JS*oX-S>k%O8CeZaP8-t)8|L_Dfx7dU5PxR z^FF7`&`+h)Zv_1(lwSAk|3zpI>Wv)scT8WO!3Tnw6{lm9b1ieBg|V`RP@rEt%L`6Iex`Q z`@H+QytrM;Bp>RuX_1wXD2^_^S@eshf zpjaX9Al7IZ8%oQ`9P z#YT+vJ(2@JfptWgcufRkCJ0jqBuv6g>uj{IXWCd}&vYY zFyyj6>;j2fG}Y|gK)t#;1*66|!Gb%hLkS8lG_t(uMTatE&ESq?6WDjMQc7=BjKK}G zAv2*&B4)KxWM^?tE$Xd3?=P!*p^`ZJ}q4m<*Wr5m)ppIFp zOw|gsWS>X62$4ks`-Oz_KoMRe^n?}28~|2M3kx7-P&96cNZ6Q^$Q*T7Mym?e13m$^ z-21^6**fw$m&nuzKmwo`t3$*&c%h)oOvJ>9L=^!bm&mc=$)VtGph;El*CT_euWf$9 zfw-=SEkPs(556FoLV}}p6QaPuBw8Gityq^0$1RQrj9q!^Pb4=dltek&DdU8405u@< zVm6ot0@t9HSQC|or(TXVM{w(s2xAJ{$eXS*co-;}Gr;DgnxUQN)qqapiTGhC>_}_< zAxbfr(o>ZY01gAu!ppKl-y;##0#D+#5Li4^#0@ne87d+GPA;z;w4@`|oeVcBZZwb& zW~9b|8Wanr8Lx-NPHJ4>PU;Gfd%18wCl4Yqm}cjJrIOgv%6w|5*+%Rux4BcraZ@OG zAg9WuChCGrqq-3nB{yDAlqoB_avoyt5}3rvRuVLD6L}+f1&VF~7Tpt3AR93@f}3kG zFfp?+3xy)HW%a1(TO4jgskb*bP*@Ox)yYS^S#4}L!^%jrUK(cO#l}{AZmWGz=5JSL zLrB}_qs8?2ozeYgwy$E%~!@s3BjH%k54{-vX%qxu`aH%_zFrRRIU+J2zzr5HbZvkjL;8Xed= zcgwh{d~`8<=cMaJUa8}w)A_HRonLF3=h6n)Lb4 zO{V+djip{{Xn&KIb3Z*tH+Y8M`@Vej%Tc_){L&(B$zqn85I*;E{p6Z_CD`_+d}1$s z`P;Mq=(mqv+Zw%i`Jk@izuK(Ivb3YPh~#H5Y+T$da}j`tW^v_v?~9?_D15ze7LwFZiQ{ z|Hq#U-4?z50KB4G<9PSI?iYXS>`Jaab9G`f{?dJ15A?BT*e_!?@__)SJMZDmuM$ak z{e^mdVIMxpbXBhJhts2cad-IeJcjw`s6Vr7_d{L3^WDv>^YF)Ct*am4vZn<;6*^nt zDXZe8>X!%0p< zY_&-$BV(u+xRVt(bC-m>%QxS7@|A9$dz%O(8BAeLYtAqjL_lCk>~MDn04FnsiwjWz zJBh6g?X>Q5*L^ZTw)UBW>8aT!a21&(xu61Chl2|{h>1XvC^ssYXgth0sK= z;1ZdcL_mRDovg4sDHDm40BQZv3$l^i;Mv`SYT&|ZRJbUzo}xP<^PEE_Vuy6S_G)er zVz1eS1QeOs0}z5zP=fYubM0KuBoAav7T8(B6YM#;PIP^juq6inMa*)J6A__j%WTh*4FmV!M3Qp#F)DKUV4rmkHNsGvwX-cMA zNS%cwR;U|lyGmD_?L5}aX!8O=!~_k(x$pPviB~X%L>wwQo}rs!jsCg zGkI_p6AlxOBThL~8aZJQC_$oo05f?qA1NmYd#E`zk(fac^2A!oYBgW=nN|=ko;u?$ zn-_N>1gV*$gaO<`PFx-VA{a>`9*NkRlpHEajrDFbg>Xn*G$1CKG@G&Q1|N#TMLc+0 z)5b^*HUJ)iu>c(0YYzd`lP*|N3L91D+Gkx)ScSw?bZI`Atq~j^qbFA)0@Yw4n8LG| zC*!Age0>vjOlBG~k6juwCI}7iGMJU@ltEx3P3nyj9Ohv4Cn@1uv$VJnd`(YySXZuERcHzU(>%;NnX1e#CKe-m=(w1O187Imkw+*v-;qzh}P6vo&?k=@? z?KHD;qRPFeLD`*s?N!VQQdkG-05dC|)gvQqxb3+I1v#QoGKyqE*kIifE{?&#Q7JhU zvwFd>1DN&G`5F_Mdmd^>p;fxVb31Q=e_|eAlmzaAmh@ z?v=wYUtBo=eP(k0dyaoNOV5-1+3}7x`ya;krpF&e(_lUjJC#iDX@1|knVCZS$9H`NutlvU!q^_v{;2{h8m!C;nOW+~t~Y%H~bq z`iyUG)6%Iu&xdE-hmq!^6n`Dt|1fu-WBV)NQy=*5KgaA}cauPoCsq0~h;p`t|wgZ=ICOuunAy|AL?Sn;3nPm;Woi{Gi8gtR9c^d(T<@ z+j8TdG<>y@AC9lz^J{PV&JE`u@{LhG(IGpg7T*2pbp9)c%NKa{Q_ZEvrMsT)x&fZ8=v34`Q443pR4#4Z~vkn>{{3Rh%qFbTtJQx#uWD{0A#2l zcNNdv&Rz4vv-|HIzn#w(a#r3N%2(idrK4qA_OMoYlNTlzLX`E* zD+o^!3{3xn6_|`GLPPB07N`YHKuSIk5&4?wLE@l5B*cM3L`)vXA*Prbm*A4tV6C;+ ztppkZIL44z$l1}bOb|EWTi|S^18ddhhKv~QPNYIdsd`dbAT)4@8@pzs!N@H@1Y|_a z9zp~saHYZ}vctf`S;QkzW^dRy5XJt1l@H5iw8a>MimMP2Glu{UND=ek$5W{G zaTCD_RTZ2gtErla8WjjAFnah3EMyg72yGWfBki|KVN){AW-ITVGr~a9P>g-zKEWNP ztfs1P13;OPi6Ws9m{k+#-Zx zKp2y>6iU^E z0?V#SfovjusS6rdf{7@HoFgrgk|C)O7iMoFjp|@R86g#~vl9>qX>iKH1(wz2TrbGE^K1ppREMhM#=2Z~eW5+OhYoQ&MCaG!%z*Vt189*BDrTo_NyhE#IHC41@6 zr?6hPLt=Sk%sSIwVTB%)l=tD1sc!2Y4iMCH4^~vTSPQEv)Z`GT1 zsWuGL$Qs3o+0faeBiz2o&AqejwCJ{XyJ0iFah@g%e=XA7{A><1^Q;oh@eXwGVf%j)^YqQrz7_Ge%>+oYI{o2gb9B-uZg}fB)shL6!dKN5^-M z=N`XJ8}rrJ<1@LI%zMvJ`Vq8OJt>XHtgT^XPmcXJ zPQgK5YvcKG|Hfjme<^LHab20*7%KJo9aMe3wKcr?mj7RV8J}ELKlZT=ye1cybZ-k+ zH*oTh9=yw4*R87M$#GhII2O)3udLE`c>iu1Gd}qP^jPVieZw!$!>1;djrjJCU7hgF zT3A?xj{E#}*Jqb7k4Fzrzq)+5z0vIQPUX1tlG!u-{*vLVYuD$u&+S*fhHGu}@gLnB zy%ax)xHR?~FF3BaH8{UNy!}qs7kRcyPtFeiV7Rx#&FsMz4dtb0^!CG(|MTxWxarlc zXEvYMga72>y;m=`UpRbzQdj%0Veey}c5G#S+tF9pstD@6quHd%ga`mr892}7^te1a z%*Thvi;E}6r)Nj^&&m`OVvdVW=X1()E;J<7YD4QSj8O#+gdm7gh{-wFIoaXq*@tgF ze*5CIn+`Q`2+m#tG(N>WCr|@7rIKMJg)B3hI07gLNu;NEXSg^CrzmxF5b2s0zyy}{ zb`9$6&c?)!b?Xz!5eA?mEA&((>_P~YQw1Uk93zJqV~8=ep<9-0%IOAYUU=SHd6%1xP8gBPRhwD36dB4Jg>5#olXuuNfDSvJol zlWJ6|z>QEQ>fpwlgLVN!kfrO~J&H6EC?pCr!1H=e4-i;D9wpT6L9@BvtaV0kFkDAu zBMGh4V<8Zmvw6zondZbHaA0IIjpl(;6XPz2u|hJ1BF=%N5E&8Iij*Dd3TO;yI3YGp zHAz54}^<)XS0)R_Xi#*M5yMjWacEHVdBW-B$1?I zX$e{cee|IeQXg@yiOGr06JjeADG(uh9LiutU14(T`V}SWeCWc;yeEPyDRtQw1CQNy z7#uw$3g%D`&}fVTvfzM{6cSLZK*31N#Nb-o3Jl34J++lU1YjD-i<=vDf<+8)tKG#j zpeyD%abT~QcMz^nD8R}Q)I|e)UCy8o&zxr-9K>XSHK7=B54#wA+H>YuI25JAz7Sl1 z>oVK3yO+nb>s5n(oTwDa)UT+VG4(WnmozYPTr!_f_J~;BAlhnptELpGvhpDfi-;z2 zXY-?cx(9IBxP1n?cfP%g`0?@HIaS{oR*`u>?JrN`@%?fTAH6V{e5t?sE7o3{JbJ#q z*5KOBdYdNOkE?6ju$y4Ra6wzki|4n}_Akdz{;lxx$5{UzY+dr2u(|2;vCrPkhu`Q%6&9D$ z_dd+@omu-bHveRO^==sdGR@UzW-$3*KhD9~q^u@$P?t_D93U|0aAU;@|-eUNHX= zwVF;lzWfe#-;gg=%kORdCnt$H$A4+vRK!3QUf* z?EnV$0u*Y@$3OuF@*+BjJNbHRMiPlY6bT?=GDwjcK4Ma}!4wWa)?fq>AjBa^NC;vq z3`_2V^MXSGd_93?f<%{k&De@;i4cvek|MG2+6PPm7uHA;5nJ{NNvo&_5#{wZ8bq1A zqtYu(vtm%_vbX7gF?gHe9)_E6Qe6R@BI+=>^d5#_Sq709Luf+N#^6q>fykJ=u`ot5 zu#(j9=+uJ7-WCO{_fU0}G}3I!Q%i3oL{@?rC1(mC7-tvgGU%FVY$!@bbMOhvZlG%% zXI4(NuTe%ca5D*$w(7gE9ERdU;TigKp(?ebdZZ-T>XnsMv63xYj*8l-bsl@_a=++H z_R^6K9@R$+>u26I5+5C{K~j^(`>eZA6xf<@Ml@n-nZ}Tb2g`jK zVpi{bLT(YpOkf1J+Pv|wg*YaJb*V&wL|K#|Oewi1sF`JBz=KOud}0tHu!xij#Rn}K z%(yr&JWoAGLuDvl%nQgJsVml-m=~yxOm>iZvvcF2_+X`uaa1*Kj=_>svat`HYpI+! zh*ImNanTZUHg(k+*1HYSeB#`|gVTD=(kRBInU{QKlc-cXBWMxTHTr{yDMaCz7$El$ z$y?&+;#RBnFap+KVyNT7&}FV04(AX^!zPZVm3 zF11UuHZ)^^4)h4&LkiA`X`EHv^}hS}iDKfd#DydiLg0#pOxGwoMkFYx^q{5Hq*gm> z=19bVz#G+#l1PgwhTL^SF*ApnyztC5HR=pKoQSvtE3tSrU^M86Jq9b$w1O7btP8dC zf^6<&g+z&zf)S!|P@{E3&%8IDnOzth7Knm`CXf>qIBbB5CqeC7?)z3E1|W=CcF9Jh zfum4uWK+k|vzD9-l%k6pkqN0s7+Q*B)&zvL2oliVt&fqbAOIzc&hX&<*p@ALX(x{^ z+^aaAj5^vXX_#?$8iKP#X33C@hA@H;)#{{44PV)3jkGVgqr>l5zq837KM3zHPk-h3 z@z(a{k3Dna^z8gQ_ipdDF43Q@!N;tO7DN{rk{=J%NMkl z@R?`vq@G%@uJQ6HoAV=>H-tT_(gGD<^KQI|v zncZ!U_BNYKw4EYtKcn+NZ@n)$Q~e_1%YJXiHYa?2LuO0wF6_a=zq)WSc`?N|-Z}ou zU%dBu+V~UuHy#f9h52-=jvsBCv(?e})4kbzG=29V4%m5KH=hgV6xxF8V=7ykDt+() z|IQndYWdWy^sNWSfB84>y~dkA_596lyqN7g@>Sivu{}=ppZ)yCcX9q7f9=j|W%rL= zd#P&EbW}F3V{3=XU?*m$uoKcCSTU+~qqM z*L*6Vuoq(x5i+rhLzJF^1$~_wB_bvy;)Y;FhwSUwb5^L4z$4ItCjbjP1_@E3FbC#h zDB0L4h!D8|%fOj>c5;zfi4rxcnPVjCx;h|jCE!N%@JzlC*XeVw#b8u&t7-&;a zv1L^*9;q|lYbMvXw^p`1UryCZq0&{d*a+=GJ>G31jB+Wi&e^i^5-O(Ah+N25%Wmjb zx?r4(I8yX}?0Fxu8PLS4Yj$TNg?UwU3VZA(MaMaWR8c^trH~JAGoZPtY;m(3z4Rsbz0G`$!Y1v-mTbidzP9^u6;oS7-FvmC zCj!P~o!QF$sUdsKLpKX-XdD&Rx(<)bNu*|(Ff|3C@VJVbtyAuKpjg;582|}^nK|?_ z?gI?L+$|etc8-y0lca%?JD{LaOWLlgQAI>SND@;3#5_0>P!rZY|I&1(7MyA_Az_9( ziube9AD7&#ll9ACH6I`dl@QCVYd7KUq$I>DWRVdAMvQGKah=5`c8KMoC7+W#q2yeG z6lHQ|gOek`139x1TOXxM=>mLi63A=fHqk~C3(*Svl)EQj4*P1?%t2Bl>;hLDfkY`7 zQO8(%88m00Cp8uj8<`Sd77Sz08?FJ$tbq_I7@~8Ms3K*Bll6z!4M0j1?CI0y0(>js7s$nE0$-Te90OfW^<)C;xalS|>`Mms*X@g4c_ znbFa!>E=CLd#k~unQiNp7S(>W470n#gS`g6x&Qcsas0B5p9TL+t?#9se<541lKeD> z9lO|5YyuVKqxt=NXN_Q5jShbw>E~DZdDMTZx_Ky@U!-yq`KM^>;-Y%pQ3~PcGhAEq?Lg>hn1KE92+CMxXwB&B4{J>WAwaBX7o@ zcE}n!P@fW(gun>Q6X#uH1^ZCSyc^BS!?_+GU7Q`C%^x1#d+_9~>b%=N@4MM@7?w|p zzrX4duq6b_DiTC$)-Cb_1gICA#v#V#&gjnL%@002Kc7D8Py5VX7#iHd+O$9pc6T*5 zCtizxoddH73$u^}g0KrQ3$rw!5ru)V5>U(3l1c=HC`jTHrY&X|hiQMWV!*B-Ip0oZ1jlU?mzr<_K<+g00oK3`4Ia5E6$-95{@KMxax}6DJX>L|jaR z78u+EA+UieqoO%*FD>x~Z%wR>+y@_4E)q;CVvrybm|be|Hpy!6i&Y-8?8F#gk@DIG zQAM$enPNliVbpQEC4L#><#KkGI}(ne+ejPxakL|Gi`rap}weC0@Z6q3ssF^95fn&5<%hM03@s#pA?(}%*D0dwc(OMi}%?E!ivG$Q zOIf&+JNeq?Yrdw(`BQ!?Q)D1QD)zt}*duvGTvMC~?ZglOBia<+rIyu%vUBW1KqXuS zCW3qKw$f@CKFfM#&>8kw8uGv3H2YEdw3ACm-O^CBPRFx=8L) z%$Fnsg~+jyHIdVqC`cTWY!Ct|v%$QIh9wVr zD4Tart$FZZdut^xKNqTx)5Rs<9@GA18TLtPtj-tHmExz#x?p!7>%V+oueRxrzBGDL z@_+N;{MxAb;p^MyYTq4}O$;wlb@$M|vef5p(yhy+>h1`YPl6xbr{Dd6tt~gN3=iI- zpZhg_Wm5l}Ke>H-GyTE?y}TEH`t{~({n`KW{RhwM#-C_z(7At@=httWgbW9(kBh0o|LSGh_iXn#jx?zzB>=viFZ|ahUC_xBK(c>HPT7@!{<7 zNq2a@eD9(h^|{ATR+^>^p;;;4rw~*0N>DS!NHKv(jX}XgEJ!V##{ z(Yp~*BT|V~f!k#421lZpxJkSfi7*7La^O`Ea+89^q0Jw;BOx_JfS9?9yLzNj=e#nS z$B-(ky(8+03KN*9uFzD0Mir5ArHn*{b-K*+jGDt3u}0jmND9i<&&$xOs(CT5fgM7N zz+{f0I2BTt=!D=Ho7P520!KLwTZf@HJ!}WvG~OU~3Xx<_;v|Au;JuZFl~w8byn+!y zT#58)HI}M0+H4WQW(AlNC4{8Wv*uDfBO9nsp{q#Y4wrx`k*b%{yJ~Mb&opH3S+XE9 zM~S6r%GgWMu2-d}H@D0&RFhN>v|8E>=1NFXZ_+4^(`dKSs$cmcGLZ;bCe5y-fu)9s zLm)vAf=G~P(wI(jIUc}Hl>r4Xatdo02i6C0PrFD!5{HF!k7dz1YGY%vAZRPzZcr8V zVJJTD9Hv%^o+6(!$RrxfUEy=^Il&BKU{Vn(KqeiyEZK#qCMGb*G=`k4CuC+2)U2oA zAv7UvN8u`#+8tcf2VIr2N_1Y6SPZ#!BYt|AO~e$})iPOO>q#@b1Got?PEKHTG6aQX zF$<(%IJJh#6yCwjwIhY+73a++Zib2oUJ+_SaEIKwx({FH2Dy+FwkTGo5%B~um#!OV zaPP^oS}{bkk$FqhFnW`v1u-P&WJran6sWm6vTQ7dy@f{0jdLVJJ}blf=O46_$)$}eJ9+vHodflX8sR#`NhOw|9>E`5AIw}A#X&<`fi@=b z+iCB=FPE=i_@TJ{FkkuRYI1|lJ{v~WdF+o*Q@B@GJ6|j>{zBTT_VV83hGIL#?X4!Z z@#^KyKfdT{TAjr9tjlN1+mzpk!$;o3&O6P!*P`9h*DvAVm3nJ4m5k;xs#kciJb&-~ z$6K55=eO=W7&qULXP-y&v$S=|N5wB+vi=1-tL;v)gJaB|#5a!o{9$+LCfa|aP9KKq zS84g8^B==@UgU@8XPbO{wch$g{^;MKo43e+k}hqa+MzlCg<@IWr;A5{GS&Qk+5FB> zUW!kCJU#!RKlerMugjw!@Mi`ztMGv0MUl+~o*^1vLfY~g0CNs}%6#dAGJB{0;0mo? zi7)?}yz&n?{3IX#A86-+UHT0@e!YDCXZqLQ$BSQV&zniyuYNdVH-?SSLhW0On2*>3+mn*BJ={whEJHN5n9s$sJF`tuK8xB`CtVD@BZ@AqT- zsc6>?J};{>R7%_MS#1w1tEAqL=oE}MR=^B-uBp?#i~EPC@17o?K72Gh$;1_? zWXf3_05bzjfy78C5JloDqK<^BLj6=7#1w?;61h|qNFs#-^v=#KQWHB7dm*x50U9Zj zcc6j9JX#s7ScKPWH66zkJVj~9#zcXAO*c$THBmz$z@x99G!o$;U!0(bWJ8~GK(2_$LuG=(T-*j9x?)AG=(3pX5N@zDFc24# zo*5xZl>&L=RFg$GlbM&pTpsjoTgoLLHZ5#;ipAR;Ca}Z`E3RtVoAudva6k%(mAFXd zAVS=9y=-JfS0=<2ajnDyVpb~VmfZ*M+!knDjod5e*12?+S6$~eB_GHGJ<#74cbvy zFc+h&I1jj}EM{x^r0~psL3F|_hFV!fQs4|9fCc+hN<}h81y#>OcI};VVYN!#dpCEA z9=FXdSyf1v@KVsL6gAVKrfS3~ds&%Nv7i<~iI~|WfQr(vKHWDU05XPcxoN>rL~S*A zPyi4(1(u3Pn4N=DAZC_G^(2nBLUd59JWtGn8{A25-MkE4TAmtniB%?RM8|BP!RLK{ z;j5q&P0*XJjNJ^nmi)@xc&;9t5-=)Zqm)I-M@36^4B9R8e5siR2o>nyq(-2%{@K^C zx0J!zRB1x%n?x6P7j^56)x0yT%vgtDAR-7wr^=wFMO|PKATyXZ z(3TVGri_?~iF|opmJe#%kc-V+lk8Qgr&M^93VxMoLX7J{;>RVZ4Vc z0j1eHAD)SE(nelwZ_m!p!_l{@zB_6sj}G5V|LP0v6{Oc+-!7NO4?HxZxU=6{8wp) z-}}w#I`L0_BHWMuPw(X&gwGz-eJ|fU!E*^eu+QnhebXHrobMj-8}CcM8ZcQd&eNOU zZLbpl^rvvr(!csH?f3d)x9po2!+-F*t6R(VXP?_^o9g|g?@Yo=&sFgf$(Am)94*TH z9-qDwdp^Z854*=3Z~xx4U8awGs#{e2-EW6YR6q0C$sgFm|MkCq^Yz)mtNO8&V*PBl zcNNsKz&&BEm~}8Ei_8sjKv)r-^Kc=va~WowXLNix%$^MAckbN%;O*(#)6?&E-SNd? znHOGNjJml15ky8{S~4yKE|mmgmLSOR-fcy; z0$dbAOV_D8vqo8mPZ|!Rki4mB)EXDHUeC-A&6rRLl!=&Pj3EXG7t;b$^qno1#%t{& zS%J||C@Xr>B}36VW#dfId$PH?!ijxlXh^%}d< zWi5k}TcNVJ=#L-u!;GvKhzvSLiE5$Yg)gVs+)Ef`sh#_;wZs8qCyO*IZ&J8 zMh)`8bUg`XxSH9jUk)o9dv1y0QK7S3W&;zG1n;%1N`GRjM;1DdASEo=sfd(7fuy2B zrmE$_^a5Z`X>FV_4;BK<2h~nzSe*lK783VL&ASzbm0ge@1uxOL;!LdO0T6Ij%DFpV zEDx8lG!x$-lpKc+y7FR$L{li^QJEx=O19)Cu9+&2-Xv)w!`&R~$tVq=t_1IeEC7KB zE=sl#(Lk-EQsZ1?5DTKwnVs3H5S!$KmQ{hdnUxE}A-M&sgqWQf1U9o(?|E?NNJ3FL z6i|}bVBF8!r8!p`NJv_y);uUxA=J^Hj-y3LxOGbInoWD{yuS0?AELM z6j;JdLFPd@Dz_YJ0c=1)FnY>8C!*jI6+=PKg*Xca$gt+K8wGHSx6)kLjkwrpe0lG z^5WSo^f#Nk-RqO#dC) zx##uo;Amg$qx_K*T)9VA_JNmtvgz~M)#ya|{(D}&>GcHZ`C#AmgJ0?SQ|SLv*m+}U ze{Q+Fg~$J9eD-(C&A&A?&!y^5RIe?uf0stjA$^+0Th=vdb6OpT>Gx)jzkV2>LHpUw zjo(WfKUeT6O#f2a`?hcWqkR8!diOu{SN;H>`upRL>dsjeC(ru9kA&+DH$816Ovlik z;*3Ltj9miQIGwW{vR)7_XgRw$yEvLYIX!%GK_48Se1CfM-9CSF5lZ$Eh0$R9eK~i( z0}oBCCsnMgaj02PSfX$vIeX}B^Yq@);oE0&c+w7s74yh=0M8}BNdOMQA&{(DN~|fY z(ZE3@a_pFw2v*E3L`Mb6@WJE6e9KJ@*_oqDWrRRa^A%5d0qB}I4=tVzO5>*zCA3}IrS^(gs*a6&F_wTB=aqVzxqbSx|R|IzfXzt&~h zc^~#XW6U|%TKjTt@4c$ts;=tluD-KLHZN+5q9QAjZN;%2z=2~JhG8HVz(C;SgTEvn zf*^1L*Z~|n&IPs=$rt!81P(<}ltRgpD2i;dDXPi7S9M+9`+MGV+h^~+)|zvSkq@Wx z7aW|uvFDm=jOY3N1e&1fiae9ZS%%{9F4aL1nOvf+9X8rr+Y9&;LxSYh#`uLRjBPM`dBsEVh zYAUspLJTxBpPF4nTp|j%fG~n!ipWUjUJ(~6uf&y7AQn-fvyUz8MT*A~Vk~oN9ziIn zlosBiy>xvXh%DqP&*Q?jaE65kgVLBPkG0L&CPfR(0jhA3b=CDgo9}M)MG%BYh=O7X zT0+fz4mSx+THp#1)s(~&j1aHl^U%1gd2hIe2jHwP7@sJ6O~^tp;0)&K12qJ;vOiIs)Fb$-dF%?vcd8UUOY;>Q4irj^U(OXh_7LyRN z6K~?79?6klC48u|H1)8G8qP3*e*T>=0mfRX19K6d#C9+-&%vTn3R_N90jA74%=0ix zVad{nnu(=FZ%hqgvBPXky=WG?=E#y#q6jsTGpvlypc7Gls!I$^o_HOJFdJOYfUNgkPkO@ z{IRXpHr+XzmJieMh_o--ugbFPMbrAA@UFF!YTM+`k`mi&ceMMNbnyNUxBt<%UcIyL zzaqzf^zpO*{n?McarfjGSD$TLoGy2FzR(}tUB*rr&|@r@xH{W@|H-8u^O&gJ@Ld~oHj9jDJ7cb!fL zcQ-st7w3H!yW`JYz1rvB`K?8_ub=(mG#qrNPr9RluYZyKeJ+-mN89b})me)3a{7}O zf9=N~zS$psMjrz0=yO+Befi!Gm+PVa@-OawGRI%}+qkt`|I*hV?BC44m#2sK(u3PQ zMskeHh9@&}lS7r zbCh1ti7r)+O1CJQD)V?V39IT(v;8ciMMEglap)lsJ8##zC$Dt|qX?5|N~)1Fu#o78 zL=oQjA_%%bIxXHDyT%9GFsUi@-!fWL-Io*n$^Ip>{+B8Ss!Kaf^0hbzz=? z(PV2k3yuf^sOqL}iaA9`7Ey^#TR&gz{qou&o)bcVgjsY)rqJH(#&p*bTh|hhK+Rb@ z2`b%_kID3+$|n)Qy`_jxT@6OJ-phiW!K+iE zV~iA&#G7y2YOoO^BIqI)IH}NZ5g=uovw66LkIApp#wqsK5a|}H0dlkIk1&8k;?m}j z$*_0XIvSIU3!ni*c6H0*#cTC>jDfNuEWCmmod)I>Q*{;TQm(KagWi(6IK(~N%yjm& zhmGLOXa;X|{lYun;7}Dcp)ds8rMS+K5kpAF=nz#oW#+Jj$6&3G$jOr;iwwe^nni+I z*_kk@Bn6c&1QSI=Qj*e3$s#E+h2@B4!n#n=7;)2-%7}zGAdVF(t_;AdTQU?64OckB zKkc_uGN1y4_mgF3@$T5|HeSs`dFm%A<8T>; zWm(o;COaIUyvD_`&FiSo1V56LWf+)K*{5>!t^LElvA?a0M+b}d&hT?}@nCt9WN~ka zgLl$!w+l@}%+~FFiEY-^)9cNLmmj{-pMG%>zm2zkJ?DFrH^w&(fp^Q+W`DeSdJFA~ z<&CHN_}y!}oyYfvxBpi9#($dMd^a8ZY1}!&bVNBuyB(YTbpCvQJhJWkAA7!zc>Ng5 zxAXF2J^ZcMz8mr9@No3?W8Vz?Vc}Q6H`m?57t71^=AFo2jKgD0iuHD$uJ`-GS2-^~ zwDkSyhj-~;(O>$9c>CAW;#N9)BVKBXW^%oZSd%}OW=@)VGFO&z5%i(Y8 z>Wg0faqkv5YdqM+<{Ce^7{7OU^^N!O_74uS=B(f4DtnLaTRu*BUyd|=zMo-@>^g{9r*@zq(?)3bc> z{N?%ilNZmqtwYVz+-950f53LzcjbWu{H6g}Dp;@lx zU_+ucMr1>2OcF)nIO=UUSev+r3cIDsT9GIch3b+85fR*rZnK^g-p{!ad!z<7#Vo?0*|qBina}F>eSYwt=KxCN6tBG*Dsh)I6TaJt90sZHdk-iTY@XSl%eYn zrR~z(Btk>oLrlBY#My{hy-QrC7G#CA7EvPy3XmMhAr{)uIdkM7T@*r5?L`J8ud}sQ zf~&|&cqe{AK9lWG(s5e%qJ7HE_On$XJIaz+us7Ho5#f#suk=~G71Jekp@6uH7s{Y> zC`HR4T2w9)o>MwmK&xK^QFn9 zGA9l~00G~*T{#A8E3Y@x(B`#yOXfKwNluz1piwlcIv)wYm$g^iAv03p?Gx(jR23Pe zcnR-7Ktg-O3epnh%z(QGqe;ZVbsfjm$Ju7ugfnafzB$&Zv z4o{c^b|W&>I&LB7tSjO+mN7M}=H|_U8WfwVPlj!aO%2yzhK8qb507vQ59i!`bX$=} zkY+K}(121V&Z9<`7mIwjFWB_RKp&HYYrvo_(%$_#FvDEJAP#Z;(!c|j|nEV+!U6= zyP`v99?hH%@n{pUy)mby7{z9=hMURYu_A#mNCG~UlM_y{@-Nya&RVw}soquwG; zn7zbUBJb}%9G|U+-`FhPTP*(RH*d}N7WEihr{lw}?$YH`fO}bOx9{uo-zr*#Eb-*`2@F8Rx4k-Ip4y@tfahgA1>Uytuz^WXTf ze03IExV++i+V61v>UsL;`-6_t{lk5|&>wuaEH(Y&*ZUVO{^~!R zUZ3=xoIiNg{3nT*U$O>BR%iiaJRntqv`Me`gqu zfA#f)^*{IXuV3FE{~*6y^uwJvY4v5?`N>w(?|lED+xh)3wB=XR=BONI`P{uUodD`P zk85HAr;ym={0iFf=upNHn+ZF&%eg(;oc-wf`MF<2yMFZK#rfkWZCiKO(yk1XN50yX z({Uf0tE{Q(hkoT<@5wWw6YY>Yh>bhD-pn6uUj6XNW_}?nPakuyv58KSX=%rJ#yw8VgTV+uhN-Adc%4#jm(Ug3s}S;L)B zx>5#U1y2l$6lfP7fJt1^UINV-Zf9eCx%Wnhwk`c!tS08pwfLajTJj)V6kT^All!hh zCQrPKTM0oyv;!Skncz zpP*R3NCSZ+pcIMXduwxZc+gE4$`W1I7P~rMwUHhKVltnbC7%=6i7rw)NV-bmh#G9O z+6;nZWRKoy6?LXYs4zqfP;?b}Gux&boebIz z^98*k6tMsgA<2y~R^Lw6m(+LJDGb6BV`izWdq zB)|%-$XTTK6rmn1yhVhA=IY*&14I#49Wx7~Xid5;xzA;dY_%6(=pwp>2V-=X7OA;S z;Uj#ZJW4J|&Y*t&{;e<%PZ7~z9>uY2v8ui_!r4MlLq{CXcE=Hp8U!VfM(U0!Gxef3 z9A?Z-_tk{8Q-eg594L{Gz1|5nZ=;*mIaeP%24fq&O?0=wybE523X_V=(8gpGXIdd0 zAiJZw^dXB-LsW<&zC6bD z?fvecJ2*MHKL~p*6UL4Dh%`#KBaMC{Jfe)nFESniKcD;S;msf9*WbkWv#7@&3&#QC zi2ii*{QIx^mCOCE{?@$zD_8M0`d?Xhk97E*slJ2xFXYv1iyz_TEk1p--G7Dg+3@38 zVmIABz}?RbYxegp)5Fil?$5{cdWhhb8QT1k@qzHoJvrsyxs}HU^3^lE@sZ4hw(vJz z5BoaWNBHBv73mH7FY2uy@vDEAUwlaUIuE<(J1jZ`QLpOxtFZ&E>z;m}``@nXL(HEm z<$H4Yo4)xv;$M_E{vjUz$Fk1y!3mBm9xUbFpevKx1#JyK!FI)yNAu~2`+sy7VNLIL)1U6%TmuDIs>Gq=|oW7pk`E`8#FBki{^!#6HKU1*S3@>B)aQE`zU6ik%`1ex!Z^rh8wEUl} z-a1Mr8yW7|^hMjN?XJBsT0#pZW4WTdWIN*gfXj*7iJSTIZ1>{X_QmP$^m%*rY(D$o z(Rux3!|SPTb{MAyTX%KtF>ktbR&+@DAl-^YPw0{iSv%w*277YZ-XCAR_~fI@M}l-L z-AyX623tr)j1l`O>J$OqAQJb`nKV)niSCpNcqH{4w382Db#$G$OXdUdx=aP&3M*=!tY!^izhA8PJf-Oo^nyjM}xiH6Lq^xn~8%XyFvY zHs&+#={~qhix9FSGd-n9s!&B*0Y?Z@g4z)s!$ckYJioI*~6sY3W~!b_iQ@p;>Nrj&-iScB!o@>d zL<@IB&>X^6EU!|^O3`ua4WKf~9!5Z2Q*btGZiNOGZps?#V)`nYGbncCqjn zUA8VBPT#>NVus9+E2c|HYw2#QyR&)7=(6>xll>A1k%$zbUNzR^5K10kI6}J$oVwSL3fs|VViuVtH0g~gJW&=k4Koy(h&|dU z03{;!&V9JJB?I7DaY#87sdKmAq3xesoOS8s?!m)u)UubEIxlmg9(u+T$dAzuG2Vwy zx$bkilNS#b$BVpvfb<&jfYH2G3vdZ}diLtquAaoY|H5!|y1#m{c#*!ie&haw9MfWX z7Ml03hQ7y}pG_Yh)&JJE2R{7t^5i8D7D3a*9ZNbzZ3t5zv^E;)Q5LcAzypQ?lX)P zC6I-CXIFXtX!^Y$?=-qEmxJe5^|$Nx;7(btS4W*6y*}k4+fPyodG9Of<3s$FSN7(K zy!ZKJOQ{O&X@#dR`SJH<^nJd6wf#Z)@&9Le;PuUKPFJb??jLq1fiIold2IfmKl8=% zEBWrW?SHSFzPav?aCAH5wuY9L`}X3oeCxY;KW$gbi;L6zN8cN6Oa7;ScJbKS|LwP~ zUr)oYeCgyz*DwFgkKTX1KmL=4pB=~jtMc;j!E(&Ejv=otL$XWQT>X_q2m;JPQAQ9V zRpn}wt4pSlub#ZP`rwo2&rct{d^KI1Z7yEf^=6BG%e!6POv$z?H9;lCT#{#9<#|YI zX=MrPoQ}JkJkg$L5owU zq6j6F=+yfdN8eqy%(~FQ**6hbu!X^cpf_(ZNlb;7#WY#UEF_d672E;Foajms(zQp5 zE_mcziKqlAS!FFTfGVkzawut~x$pX+hX-cI9N1dSktr-WEpkpM1YB}cMvy-9rnML` z`($>3Hf^_8msd@sE;1y7p`Yf<9c)l51(}Vx@~Cvn4rw^_RU0eQ$ipbTLyU|dGicEq z)&M2xk=?^x(LBvjOjL>_>_xT`NpdGG$+fE5(9O%(hfU^9(2^V3A_5Y$QN>bFBqS~| zKMzh$@jdq!X=|O=juIhGG3rc=R%@eEtMp^FakBk1PL-;CpGnWjINbP>NtgZ1bLXmC z93HNv^F5}B2oFZkqlP3&x$6)6MIER8cB`G_wMM#eVf8sJ*|8*euUZt>;v3aKPC?NW z*`1`6rL6C<9@w^6@q9QJN|KbqLfxxiOb8)KWYHo9g(N10OHwIWlB|X}sFR7eRxoFX zn&eqD5ZpbV&L8cAfu3f7LD530!vRW1bd0g1av4Rr&#oE0XMpU z>QOuj;@tg-yFeC9D$AsiG)e-qvSX*(i;#npTaQaKxOZlyS&FH;)o`;L1gR%@AFhGD z`Rok+{KF%NdMnn{kwav5PO%SegqChfc~smuEIGxEVo@g7OUi}h+w|9Ylb@r+EHdtk z?3Ci3T$H9V2T%i-h$~VM8FdyNl@rh?B9Wxz;9&W-#2rblm=hI|-HxIi2TrX$Hk%o< z#2mD^fu1v~^vWbGs)O6$K^0dW=_AA8ju;UmkQ~J%GX{ynki*D(k?Y)F1RhPF930%e zKfJlu*sa=<{cT3TtE_$u`A0I|55D8$KGN3OgE)T3uRZ|2z-ho{1ggvI=WI_{KYwMm zE5APABJB>A>>jS4-_Nb9Z!FQ>8?Mgm;(OQY&tm(hx@NOJpEk=*AM_75IDD3QuRnS- zy>Say_i=sA^q3;!hyC?w;bzg$$Q&nk9xHHUMbhWmpgp+sCzpWN0;TkM(aX*Y?h4g?q2=&`ST_AZ!FXI zc=UJX`m@;nPt)gr%ijFAaQP+sgMViC&5!Wfw+}u!TwL9?FBdHDEDrPP)@AM&p|{aR zY)32w)?)@c=b%I;B&zy6UR|HQd@^2r5}WqH>DeD&zIu5%o?W!_xt%-0xpZV$#;h5t zBgGlAt z(8&-7Ndn9>H~}sKRaF;h1_WTVCXp4r%2DtZ)Wl}GGv)wlqayd@Co6fJ zkK5D(h>o^LZJ$C7=ZW*gaFBk}nWT%RBGH!+fq*w#z%IC8PE-+}D7#2gZOx)-v}~#d@)2Cf6(d19K~XItL7L>6V=TFmE;^TP%*SKlIABtrW$H^_r<5F> zX(}3hc18u}Qd6J2WCVbagPd-4-RyN|o+A2SVhCU!HCzH39N=sB^U5bJCCVTPXhA{l*yECV z3S{WgwIAozh-UN6{68TIwTM0mdq1WjaN;@9Gf@~F8pJhDFq1g*yEJa!e6Tp)f|HyI7;|%r-4T=!)wkQ^D+* z+z^w)&&;olS(sGfh6+s+$w{-QX62Aua-&03lh_t- zSM@*pV7g=Z&m63Gk9WWN?bC;8{f*b}kE#BlU%j~-es**Gq-r(ga`lAmV09_tf1IF?%dx|&H-Z9jS`|IbJ1IHzBDqkQ}c z|4)C@zSyUK?2G+wwC_FJ7p7aK-(A%o#eV%j(mEf4Z$6OxF#0~W&;5IkO_DrV>5oq@ z|I(|M_m76V<)qsg@9vkMDYdTHuH>C(&)3_({&=?@7q9cqe2%|=RzJh;XIBSON?E|y zVM9FIa{FA*F3*1`&mtD%t2;;2eE)DWMf%|5erfnqKPA71-M{i%=U;CJ|H2pFxO{H^ z<=?w}z{O|ZxYM8HU+M{feB z+w8V)F*^d5-Fr`+Xio4fbD76h=jwo^*mv_qKlzS47bVd%uhQ{hUf|#kZY@vbmUCIk5G{UWEtHztFW}vs$80s z(CADS)gck7qn>!$ZeqN05>i4he10A?eM%vdW=kTfIr%!b!>kgZQM*M9rfPp*dH=rC35s?7RNfuv7_V=fX9$D&rnw8?m26Z@gn6IW)!qU5V&S)>jy{(EqBp{Jf$ z2pfDNL|YOVFbA}GcDC?2Vs^}-BV(kRW~79~F|!6FQq3~=C2oAl5D6i`h*~jLI9=3_ zD7PhoV$fbp;WRLb)9YMn5rQX4YF?t<^h zkj*chFU6n9IB~wRk1L53XVZAO8;@}PdS3m+?)^dA9b|i}`_i}L-ml5^>qGl#eaq)J zN9#8H{)K&R>oRx0Tl!`2tBAo|I!%_KIu=- zlAPMxw{i5PZg`xQzZ=Upqy7>P=JDm}_UUb2zSh~V=5PEv`tx6L{>#39!@b07$K;`; zx_{BGCCQk&{!w&4w#zplKg*x~oAK_yhx`S3^-suK&#?TSo)vjA#Fv2eD#bdjDfb@$ zUuW*)dK32_6CdeMR^vaqbM>WDyz^>lC84#?-No_Gm&4iWclYaefPZ2UkGuXiwQ%Lj z;WOuW>lJS=MelO4i0L5GfajS%e!f5d;Occ@cWd?Xj7LA(-9AM7>`*?L;W?m0iqjzl(Vf?382DF?BC>c&fP*J(9dm zag=pi`^&LRkAAZG!B3vNIDI^wCY4)EIUs>vwM(To);R-!1PK9+YEr-uLT@4})J|d` z7Q}JG-I`ovOk4<-l1Fe0xjB?W!_vG9zw*gLOJpDkl2NOYD2UXQmNg}GimZ|~m82yl zD5-@-W2xFpR!OS)Q;9t@QpZjeCZUuf(vf{gYnWZ=#$X0|0VO~!@QzFpIa!Gq&?qpG zJ-G#&s7re4@qvZFSCF;&Nm#zER$vmB|wu>vrI`ZAg@IFqz57^@(|jLbEpIg7>Zfo60WTD3Qr*gvVt5%i8-44 zEV9!s=iH}mQDog^^9W-hLA_KxO#(7Ot{4}zh7>MpQUZnEqa7jccgpf5jy0qG)=4H}FZwV>84v>YfbFG6h zc{4XS0~l3jQBWuvh1M%WdLNdKVh~b<9b$l2v$@q6&1$qcY=XKBOaLm>y}38cbDAOt z3KxlyC_xw$7J?A{+{4d9tHU8nveMVAw^ zrC^F+ud<9@(vnevIXs0XcUF@bRb`)(N-xT5a*Mcjzjhilh{|wf7P(2hi@U(GAcsn* z1wri+OA>4gu0(Q?J_IUZ>%dWjx2UF-GEuInGi9Kx7`YVb&`>PGHMvH+VWJ>rv|vOU zub*D*zq`iz*7faOSJo%%ewJa@9Fil|tz#F>MT%nKHPzK3E&IIJ-K*hhLLRrn3iWgM zt4n!#+Ao&+^{}$0!Hr zd%W8653bnf_SUlf;C%mYeYkyB`#*Q@c!K@mes_P}eRa96V>F$2Yoh5(+kS6-wUn-H z*Y`U8%2z0V%1^$~w%hoFr?JR#yU5e?>%V_?xtqJgx^C5`eqKG5%iqyO!Z*Ij=lA7r zJmY=nPd&`5_(HN4Pr_e9=)OZx+cMAJCSc(tQl%Gs-{ z-@JTvUspe$?_t{Q?Rm3>U)-9zi}}sRqUhm|Lx>#~~Tk>7V5@v)6n_yDA6u;7|i}Ug7)oypbon9dCFWbz`M6+3Uh|Rvm z&XuOHtV~H#k#y84k0h4jEy;B*bzJ1@pKO2dqgUU)#&oe5Br}6y!3goBHTyJ+O%c@4 z8yTsFD3nSnJX>_8WN`%;pm>!w>o%pixJZQJn%qGsq8dmUiQN~9F(=nVqRlGDQb zmAjNcPt^danv&u$<=fer=qX~^*^wqi4(YU8D1%~<329HiQFgm%rR+mDj?w602xqto zRin}-L?Q}lDm9CO1q=xz1(6^}qPMkkgSR+|`7lgFjc6Q$VQ}|wM+=F_8lG+nvrSWl zqcgP0+hi>}21@C29u^C()?Czl%QKFeuV(MWRNKbeRSPj=>~}KmlaAtJU=A_5Ae7WJ ztWvB(v+q;eq((;xF^U!K50@vaLl2@^xQJ&kF)1m?Dl16^AT$Cq#OM{A2vRd$l%bfw z9Uzbdkx1e;xmIdurD{oCR$73;Hy%bl5l;w!>V&m9 zXha3K7@zq56TrkWxNp8UXGo?{TBoafi=$fy?qsuwtkL&fw@!&oXvD0k<>mpm8;X7~ zr6TD_%UY@C(stAS)$ZD}aS&udQfktY4F+$93ET@rQ_>}50L`_9HILDJUt_M(z)D<` z8!{*&ViaRz7#+?M;yExln{jkB3vcwuqN#L?u8%^b@R@N*PJjn|wiroBQ60K256xUt ziz9N$5Q!z=1*&i;JI=gQT z;ej?btA^GrnuEA0#$}b9MYDjt#)7@eVQb#FX@pQE0yME}! z#N-pFt2F8?b=1@`9ZHaJcn1!OPCx(77ub#F+sZj(9Q=Z~U;;(KsrfXUI28(a3BnAm zB}NvWFzy5NR^3u{Z6VQ8_^SG$NwQgeRzR5qp|pKTo00^KNL$EWcpbbB1XZZU0E1wl z2v!L#BYK3IpcO!rxgc@`Ou-~wFhNljID+2-E3g+%)GgB-h)Aob2R=%`4c5XVEUdZA z?i0L5Yi@xSu>Fk5ueR&)c|1SK2M<@T&8p*&BWk#8;rlcd=o*s1p!5Uvd3sT)%)gmOD?;{|M*B{M z{q-0g;_6TE?mxg+|8x1w8-j1}xJBAUzruWo<=OV?<>e=@%kC@XjlYJU|F`qQGCX@H zJ=o&*slK|0pS(T2mSKzTA5QK0i_L2%IQi1>K-2w8IeyC*U-kour#sxs7<>Nl<>tpP zUVZrr54I~S@~WHf8#*ptT=?dbtL2-Re(qrWP4eH_>NlbP`tbUb^vOcUtV3D@3z}%&${oM__dmiQ@^2SVmxG>U1Z?`h2f z5utG-0E8BZkVV8*Z*I!simD1hD66I^=edXX4u}PIkUdg@e<8UIN{)5+7vNA*0`4Fe1ZY zyt&n|h@djEPDNzN?jY$R7qN*p?p(KMgW5_;q6knth8Eu3A{d~EcI=7_7Tp}py?Ts+ zYjBP*MiW%whI$DKSV9ceP}v|Yf&oPzSOxcP*G>ll5J(0)Mo%USb-xUGMsYGxf@G4S zwjy$E+_yB(OP6J4?p-K?A_#%p{Ql<1Rq`(DMiZ2pZ%SMwDUc21oXo<3XyFyn!&hvJ zoECbtHNS|7=2B;K4_LFP_9=c+sU4SDJc$Pxk&bG;YxBnaIYbmi*oYb05CM0HQwa!B z6*I-?#+YcMBFWHc8oRtL;{%gS}jtB zq0&6q!mCAXQKi;IK`>(DzA<eZo*XrwR|Q7ak+3+Uho7D!km2L|c_ zVT?&a&;;v*&u3h8sLLCIcMDJ{Zeg?2n2@6+EI2pJdw8NKVxzLl;!K1^1l$&mh0A2V zHA-m5P;cVqWD~3Ivqu67WpbUFG%nKR>h|hjb)3?0bg;e_o5MoW!O*7}-NvgkpcdFy zpAYil?xLHfX}4K*C2_6Stzn!G(whB6+kJFvGcNjz)78m_?=CSMmXu5C^yqHFed*WO zZ|d_GlbO7(>687{@3pgqm-TKvP~WgRlfB{)BpT$ zZvSNN|J2>XH0vb3R4;q?t@u7!;QmSc0qg(zYf^8Ut3Ug>`=^-y?(XV#H+7K`Eo4uc=Fnro6Ic>fStPN7II$)@{>e;w`bbWcY*^Ha3(`UQ=qjv3k@Anq! zP0e=K?OLuX&W)B;N~)q&lvEK2s}fyzFf7v5nV&xSVE>cfzxq+Ta`&Yq&oSv1dQELj zz4=5=H)?p&600&4NOX$dROFH+MrO>)Su80^Qs#nAtuNM5BWLrQgQE)+*Rs=Y6GB8T zo|2UaK*Ph~W;SD-TN9@^!Vv*$-jIR-FRE;2zmDy7cx^rhEzxqF%)L2Uq$(Y$3S)9w z0T)pR^WH)Us3u4zbdGLhr_`KBroBW8Nid^=xM32iCFo1(>LvX?ubKxh<7FH$8&p`$ng;bE-?f<&hnI3TawSZALM6u%KrX`jlX z>)R{4e%wrN#?SinP$J<`{-eT7HVLl zRAo~1qC^pJ5i;CZ0?B01Ca&5P;jpL|ZkG-O6K;s6NQ?||*A_H@WT7f4Ni>^cuMylg zUQO@~rSraTE1lt^dyDXJ7r2H=j!aJihNQ%To19~Y^b}`;NKaMRdH8eGB(~I>Tsdo$ zfMND|iUu0v(r{)0GK*6v(mIi)(gU*@%_Cw&1(`)bpTHd>F)auHPHB``a7jN40oXB; zMxV=|2VHR%m&_s(ut~zjy+d1|>QB9KE4OXL&Rx|zAT@OE3&uhM|B z9d2Le1J<2w?laoD{DAGrX7}(G7GE5Ga$4^H0dFlS@1&els(N@g+Pk<~w+pGaU$p5% z|Gu;z_Vc}p+vs2HG@rHCZ(;pjI{qM*f5*;mWB+Bje|i}|KFzn}`P;)ge-pp_pXk@_ z=6H|yHModjj5v+Dn;(z6e1gsA^6fK!{YSnz%I#tP#t-A|-)6t&?zMPhjrfdA7r6e1 z-oHcnv%=>*_+6AQc>1Se`C42haEtkpPpu_-M)(SFY4Pbef9T! z^Nsf8pQ=Coo4oVymB+8=7r($?eu{TK>>eI1*Z1i5JJk-U{?aEPT4vlO5j!iXeg)yZmD2h0PNK4NIn zEX8DPvZ~N5Vwo+?!2%gtqL-+oK~;58D+d4ofB;EEK~zH3H;Qw93KSeYtOAwcU5>f) zlUl8rXB1@-VHRB>j$)uSbHKyGUxq(|LD8%95zCN~-0|kofq*+_4+u060WOV)(M6bX z>zbPuvL}Zw+QQ3|d3xV=1JV(0_`uMr#d7j%I&z>yXFgl(86wCxe}IW8c;iv0)wtRAsgRwNPC8YDs7rRzMJM4b32B|Jqq=!zR*eZVcD?q!y~&dZt^Ay5^I zWG})#30n}y0`F~9b+R{ z5Jyxeo8&ohU*eU>3#Lx67U_yCyWFwmfRG6Yx5>;Af@mJIMb)2s?`t`(-A>Kc!0iS# zAwm_*(ot4YMFK2=J^)fw2+a`bX6^Pss4@m;9|kcMbcv}DQNqJLkz1sg-rg zxewF5Ll-z$#N%!K2NzS?`{x7Se>DGJet-A%GW=6-9q*?0qpR!N_tID1=&jhTJN0Ff zCr|VuPA_gho-T&(edq8t(y#nfSAU+bK4ZF!xyjkiAM&$a%?-=FVp%Rly+)hDm%QObw2gaSj+^s#KAY21)Fe^jD9MRXL@Cy@^$hCDa=tvbXHP%A z{PD;3qbt-svOvXz0j0nQk4Vat;ewkJG=wv?fkz?_#1w91Dk`0+Te6g>1EmCMVu2Qp zfV!>14}m0-Q8g5&D5MYV z;RT@)YO{Jq%IH!jwnpsC&swa+Zz(7U6&H1(k}6&3k(X%6s+7hQh~B-6FpFl69I4YD zI)s=+Q*yyr&HM1l>V#&y@}T-7C0#Jb40q}*1Z zdK;M>8C!`L@MxY1BxaSH&wECU;n5rnD46KMCZdBd5h+3gJLpK6YV0bU3JHffZY)DU zNMr2TO-Wo6Bq@g!qu7KtZmiCckAXFyq4}m39UJ#&v#W#<(|N>8*9MT3h~jcs^iJo} zn$}q7T#5@whd_ixPVC^?rBn_}S!VVkOI1VK!)t4;#t!WoO*~SdnzXw2c?=VfRX9m< z35tn0Nm18c(xMa&J!nXC;3D)DqEHr)^&;O|10J=30Wkn#5+>rBC>2L4bcRo2ivIgxz@&d z9aTm~)#z2$*%>I#wN%kzLfHjc3HC{`7#JBXd6V!CzGO>`%Qii)47Mb3>haRULj|;HxcaWUJ({_ zSolo4rfir}AcsUa!`Q}Drvy;l5D`dWN))hzK-_LTS+xQI_|$B2&yfmMsiZ(8s}@3p z`^>GRRp1Cx;Ui4MAtn}GlS3^>Wiiq>1jG}yLo6W&h!y0z#breyx{M9*V)s}xvv$i| z?=EMNA+pW7u=M%?>`>~Cyr50U7uYU!d`;z#uDbv3V*2dr)i)3Bf6Vv3lTTKCyOq}o zT$5RgN%-{!XK8^pbi2#zc6#|d($pTeu`L$^OW4!(%x^H89!uc(J<_V8JlJx`a9?0Pd(`UKtvEQTL&fBZqCmlaH zOmF`Ne)j)Wj^5Ch->B~mxOH4s)%VZF&AZS)m*4u{T>k5QeJh>*h3@qoKKC6Szs=!K z;?C{3SVZgK8N8TIpKtH=m|p8ozonn~&+GD=JpW&%*DvtS-%sgQ{p~MozVJQV{@bgk zU+bU!^Z4vTyuDtm^TDlJUccpwhq0dh@uG(ozM+?&eP8^u^}GVVdtwHty%s-BsI9sAk^WB>)xPV%%0?Hk9bad+u#7^X2;4 zqYr=b;bXhn_I`yfItdp2tY~>NF$nyyF7ru4Qr0Q3nLIrP1O5^;7~Hb z25t1lrbK2O0w;`2W|6Fucncr0S%tU3cDS{J3WQsI?Q zvePsu28Cut&WehfR+85gA$s#WaaVx~Q{3RX%Fy}+Boypmh1SSf zBB^#{4;`4Qq*>G%4bxP;TAhP)_^oKKdqkDyosyKSS#!BbShI^ZSk&rM3qc}N=V@*E zw&0E=gU>3tFqauD77mZ(RCl@}8qfl22qxwtN+y^vWl2fk6ctMHk!eES!8Zg_sKUvB zF+>B+>*zXrYc6Qcn$@R-5n+yLjzn~8u&1X;#k&%77cpDfwj0&s_)_u{?Iu~vy5H-*77_vk0TG0NtdLPsa7bm@v0KPe(gD*Ru?be9NnDFxaZX?t)_m_ig)>|# zbw>@ctQ4iB5QIiek+UuOc^>9G)p=)p74{OoY&e{Gi?|=DPQI-WL=t#J4SFC#h$=Up zqnkQ;2I1Yqz>Lj&w;7XaYT1n4E zKN3lO9{P9@c+p&HlsQ#u(amfCJcPFb?v8j+6 zvS~Q4a>%$xpWs`V>x1L_Q+sLS71FY5nPWp-s|TqtKwNwqHiplE8qKYFW6Q7(eaM2| zXcM9{Usj2vQJ8}zLYhevIK#b?qu$W?n1YOO0MW*}-Av4864{GLX6gx3ZOX*R8Ybo;ITVZ?bfi-nG|-yN#d02dza)2J5L39|1}y89 zxNe-PmQuQfrrpq=BI&D<$jig-croVNb5~}~$gRjxN(7?|S)eb$rrQgpT`caN?{6La zcsx38`DgD8-QB!jKswQt=Dn=Xaej67?ek}SbZ=t4IltUoo)%xsJX+4>r-$zPwe&lR z*OLC3`{n7=_W%C7`}<@1xr6?rk6-;KPab{g^~29Te7LCne!%T_e0Ah|`gx9td;|L3 z->(0`uTF0q_J8)v2QSaZ|LpyXhq-+2aMgRPZ@0J?pX?!yllz>(51#Nq<-uD?-&8pe zCQ9P&C7(Xx^|%GDKKh60zxb>Dmm>cUes1{$^}qW4%>$I5eXySD__$rMq?Hd>^M1O! z^=0>+i~e5XmtT|q763ng9-n*?^A^W5fBarXD1i{WdxkNa8=`|EL$zWca4*7Pf1 z>i)YYn}7Lly!gd=^-sQi@5vhf&31ZEhCg%bR$i8J;9SR0{A?S4w81z;U11e+%ac{^ zE4l&0nr&p=$-E!u%k3XtY@cth&!4{7{p9)c-StIm%hfbJ8{74+^X-0H&n{rq15Hc> z3031N*}lMf_Dn0}`D)s~{CNEE{rY~>n6XH;q?%KRfRTtqcxXff;DIJ?k~;P)st2o! zFlNTN+s?$o%>!luxX_dnq%mg3Oc4W((7orYR!46wVwAMgaHudE>GUL$ zgiZ9Cm{q6{Xh!NhNkfpIJ`XgqGN-ILr4EYBAX1Su0oKB|8lLh>`YaelX5kb%Imkpp zA(;~W%9c&Q6fMDoekJ-!RU~&nW!ecR>5URdVSuSb;AVgA=7#4kPZ3Roiqhp}j}|&w zZC-2hSItkGB+0fAfhs&;;a3qa;2r%)=uAzJvXm0x5#|Nn(|4G!pjMLgs6L?yq)ohw{I0d4SV5jQnZkxr##?fTRY5f-ia z&i%^9$^^NAdWs=c@tIV_GBCI`^J-{~wMyJvb6ng}o7bw>NjF6$athX)+j2?LdzvI~ zktw9poW8%V(?yj~Nr|Ws7trUG7ug zr#6f4(oET_t|7<5i-hMXz@(yE?yZn|jV zDY5J&uHd_f2~O@8kNrP482FR*oL?gM-UP=o=wI#M`}g&&pW};vm9PCiUia^>^4$UDUT%*^ z`&;MrOL+1>>Tdsy_6z?u-+O!c{LlBdxA?;Qa&SxXXJdU}(~;L4yI{G-c!}*Q%3-XX zZd_I9#O0dT$Lx3QY%}dIU;OdQ$G^6Haa~`;Pc|>V*FK7|U+fQ_+V=f+=}`xE7c#%p zk$IJSK@w805HDjklU(y^?vLux{*w>4KYCuTo6(U|(_AA}>xm|e#7Kf0YqhI370cp9 zR-(tUm{s;!XTgTpxi;%e#Zp9;=wPA^QXOtF12a|Kvq!+2=$xVzT7-MLLVG362r_n= z*VzTgL5GJ}q9&1R#w#Hu7NQyGkOc@4Ns*K#d7q+a&f4d&Y~b9Yn!tqC0wd|v!ju6I znIVlaMNAA5O3Bsxy&Gy1OIGuzuE6~%{8@Az+B$p}eidc`jXrGl3fgpYx4vOJkF=-G zC+>t5a7i3d#@epiRD)CSCE^8RsW?_mnW^_l{W^SfGp}Rmc0+ipsL?zu+H?0$V$6tT zDBR$&Mm|AzUB|Nqqa)R;;Hy}!nQwEogKro?5i*e~DFZ#y0$ianc;mNe@Cn?DB|%3W zBX)*}NKi&|-1I&p1VKQVa!N`St|Z^qquxicnj;iZ-LIXGT^})9QAybZE_&mPM^xf^ z%(9mb;V_>sxBK&XKabUw3{6;s9%A9dUUjQ_ty9+3B6s`jn-raJ3H1h_(Tt7j>l|0Z z{^J@y+|3C*5{h6@SxC$2{83Kd6%(c-740DPNO$Oy^DP;^Am7I}Q4YykqqDe`H@vYQ- z`OSm4+j$3@p^obFVtM|K)zO!62zVUUue3*V`qX$U;y*K+;v;5`{z3b$& zucfo8eY?)b$I=gJ4IZ6Hex2n2yK0}*Hklk$eKcMFeZ9bbD9?}AIlcKFF2LXY77sQ3 z;xBcN;Q!(8O>fNQYbUEq-T$VXe@6Ou;<%{Zz2|bzZLsHO^1Dy97x~5snq;UCRAnypjQu90Tq zwSqGg3&DXzqm7a^l`cVxNI@b&>~pRiR5ZbE&h?a+rNp93s{tAbhGN>Aks-ojp$$#i zlw$5M_hD|)Zh-9U$)0(UyTcMCEDW>AsOdnvyBb+)7GG#&bwUJ~1$*^Sh!$V~6RJbKyULh4Ixin`El;L)1KxJV&Kfdv?0 z2xiO)X0_FM(%58*K^ipJg|NA(`vG=qAX5R%4Q}W|7sQRC=upuovM*SAi)gipd$Dz1 zygsaJsT-ZpdFg`;r0_#TAGYy!>V@burm^kdOetxZHTJ#bC3~{&h*@sr^syD3(IQ+! z8xAVQDcnJeaIlknppoeg8y$NCV1lOb#H5m`a3Ului+2D^z+K&BKigQ_%>)-WFYA?r z>N@EvA&H2vgY*a@Oh}nCWYJ`pr+jvnPq&D3*97gNgaAY|RZqIm<#AdJ=|Hk+YMpWc zW)GjmM-Ms^7sKj!&9d|EGHwZ$iK5Wdh2~Z=B`=Xrw5*aYy0RJq9&Vtsw>Sbb;==9h zMza@YP9mr!a!ldwY31p_EX7T{Sfg_W#6y`?s6-JevH(e-DqsX)!qBva4(_vSi{K3v zJ?L--G(t!wNvV{q&NPt*Hn-W}XmBR)`WRAFov(C~OXlb5lo&JyaD{dr?OB9%S%>Js zmDicJ6;e~q>;_#A=pcR*x{H<^y@A1yxT3!dXLxs$Xy_s-LgEzuB)DjLr-eubK`0dc zL0R5rimGEs5D^G*k8P8!?MF;Is>IN{6I8}U%DAjxl28IJof$28&1Wh-Oy(Mk=uY520q9BkfeKl#Xb`R=F2IG?HEeJD(&ZQ$iJ4 zy`v{ih%w?dtDkL?046aDcPTNDOb=O)Z_WxLmFN(4*MT9z&G?lgsy9`-N zy<2vtMc>~>E_SfmpXmCva-2x!FpM*N1ozS$!k7v~{NMq~dwPfeKbrpR*S0K6@58=t zj5+68Yd5F6UG!aML^hSxXH}6bQWQ-Rv49R@3EyM2y1Oxs9{A9xq76j0?49J}T zxszy^lxe6)qbL?dR$rA>s$ykjMr1@rcjMlB?&)^B)|zvS;fIq14$j*FHqP36uQk6h zzTc12j&*;$+HbvZ(`S1*>ec@8Nsjl14}LHG(*JvSpVNnd^SSJrUD@VA!fjdn z=%YIP*4^+^$p5|5-QC&wA9j!4iu@O%buo`II7nVy9zOZ}?!gSN%NKtt`a84y9FG59 z=9U+~6VKnm@mKlg52xXu>_2-=E`KKfe8q`NS){edlZ)WjLf3v1ep+ps;|KHnqv`mb zBCm&M&)ENPdhiXz-%kDS`Qksa?XTkIe=6_)zP<9l_w_~k>f7mCf;S{RjJ~wgKXLw) z>AA#G_|MBkq z@1p^7N584_^(2x6y$@P0G@G;|(%@J{Km}=7?tOVX zubzIg`sCA3E~h`Qqm?A8EnxR#4Q-(a;fe4bASeDd5?tQ}{5t<}0L|!YFIzx5V z6yf)hpS5UW6rg7)C4fM*+L~E$9h`&u5-Xi1)~RDtfF^5PFRHi~*AT1aJQ^u6_F8Y;c z4N(iFk>MVI=d<7|=#{NWbb^&c=Dm_5Jc1IWT$sH&uln6~nA$2aWl_mV9yDo3!P5As z<+Y%wt|U$=Ufm2s#c4%mRF!MsCCFr&8d^)U$2_-VXN?Ly2|o*m!<-Qo9#UPZ2}0Nv zL=RV(vS@+yIS2~@sF4P5*;{k700ROEH+N6Xa}5b6Q78lQglUJ;e5z({m2Symh?Eh+ zq$TUpo+bYzFc4?JO5;4ov#?KmRB8uZc{@eh0k`lB4oqv|1FAa?5ZWZI_P~k^piwT> zzfdpK6_R%7djv3tJS9E_AA|3Z$P)QJL$#&rYFehTsx|l=CWdIl&gVT2N9vB!wOG#( zAW3``{t0EFI8_v4Aue!6V~`L6A+FdK-l=}@+N($@w2L@vv92hgJw1Df8){=^r5u$G zI&ch~K~ts1f;I@VY9~6RQZm$Yb{0TM%gK60E}|h3o%@M-*W$>SHK(LQMi{3ySsagU zBO^*^(kuYs^j5u&P$nvlkP0por#UG~LpUTnB#^^SBOa4IL_$tHT^NI2Rgx%_HIjvR zlbG*2;>Z%JC^>TRG(=g1c+ik!PHGX25z7wU64)KO&-;h#{^a43-Pt_Ilo!!YL@QvH zu1l%Lu#Z;!uGCnmE{l(hirek>k8W-cHOshA*)JaMGKagnlan(1jStpey{rFqZm%xO zUwg9p0XP3edp7v+#{5tYcSrwf{m$t(p1dVvx6N_!8s0v|iz|Hg732ipdXsbIb*(bU?#LHAZl7%qpTE5Mqs`^-Y+mlJZ{@DN9CA;D1(dl`?|cC&_3?*a{@MJrO^0O>RRSLPiSR8)lE`EhQaJ|A zxkOsQ;Sruilq&3@gPnMNU=cv2W?_b>2ywzCMW`4wWs!wQW>%G=d7()yp=x4INmr#m zE6@mdOq4BrG=zhqS#>2jOM}*;7rA_^ltXnpGz5?+eX}y>oKxyb>lVizN5l-8WJz+O zTq_kIBrA(*&%B@mH9*ob^+aTq`kc?TPtv7TDrYEtB#OGuEr`&bX@DKt_I55RLzg0o zphu3J>|WB~NhPI7AsMZ^FPErLgF%Fo5)zg7Vxvmrf--mtIU%m zAdG>ag*Qrw3m^aqh?%-|i<(bNgb;Q1kp>up;hrH1Z(^YkfvC)^2dV`EF>yM=W1jrD zyBcq9EO}Nbz8Aj}pGBb|t#Q78;0h29n;b17Dli8HOyMBmXBnpjI*&2V^YQk$FV3vV zB_aZo$8>LBGLMRAHgi5o07+>fJF2kHT$o3=yT(u2cd&Nr^JR#G32kzlJXk|!92q;M zQG$ntE3m0GX&z?ofdFBR(U|+?>H4%Grd)9WE$d&-D@>QJV{~pgf{3)N6)iG?(UaH`TB9b+%BM%x=AA zWhwWUf+$2!S=AUOEviFe#TcRwR9C{DGSKI!hsb0~q*7ES<*l$lf9k!rq;$YCWF2h? z>lh0L;Xz}7z%g+r>Vz?73vYrHsnda2Kst*>)lNe*vU}$$h+wm%t*F3Ku!Aq?g&vGq zvr8EQC(dbZyH=0Ht+ap?L?$~CL)75xF~cdOr0Nl&r@*7g)#kl>gfo!Cd-!A88=Aze zHa%~AWThoeodQws+fI?gO<*dKnUYdfvmmh*$Y8-aqxGpZtwv*lc7%#_QBJ~7yBOw^ zsg%u07pMhZAj_OOV1Vvpx`C9|NfpQX3{PjdDFU;KA?@dx$I|71S? zjC}rg)3<)Vp8ntK=WnO&U&+6Gz%NhTPk()V_N2V|hS@JzEO2v>6U0_xyP;oEGKW*h zNt82`1*~HXteM;$@9wsDUtDcIzP|h2>+64hb*V3pXE*ikIPS+T+RbBvpXqd@%Ww$`)~iCL+=FUhV7bU4i6M7L+qW1e81o*OCD?q$E(0Q^do# zkFo1c^(rwM%H&ycucg2uq9Cm)1)CE4eehONQdvk}M04s=5lwO=Hyma+4=S`nw@$Lk zA{3$&&0W$(_$pFFSV&wfDj}Tdvo>?D z?jh8alBO)FBUZwNq{Ob+)cMeenJK^!mc(9}JhH{3;Omqek}a2JaX$eSkq%wP9sLHD zEr!~x0pT|xm&96mfh-J$_H$iSe;NJ-gyA_NgB@YcW_hQ)OYsnV1JT4LL4suJy69rx<)*x`0#1ZOObVn3BeL@}^u&f}a|<73xlf%I zloSDP;2d5M<9l-<_K!R4o%rem@Ai}&}I-gon`XFr*9MvUj>U5W| zA%cwpKnrG?ve5|>5FxZ?T3b31e;~{a3y&3<0Aq{@Hx8~jfDjddD!~F~grI?Nuqv(i z0d0d@l`gYHP$GBDPv=+|lFJ%63#rwQjs^IDTw0)p-KR1xE$~(IAJtg}OI3>$)S@vE z@O!(W43c`$oJyZ{r7I_tvZ80mKrWaLA=}Ud>7go6QBA#c1NEn0|GGes5G_Sb0cUE6 zN;HK51~vi6o;uLnnzdko4oFbKeRdmxBvE zF*ceRC5v#N_7oMN(C2U_l)9Y5J`pS;zro}6B2di*ZBpF%7Vy~om3u@t%59sgjz zGnC_Qv1s{cUhTfz>wo?|Jm}NUyg%HG$A9wTWtfzgPlH|ZvJ%fygv^3{&{Nh*0*u!`3E24Oy^(ysp*qd`p-Wu3#-5M?dlKG z?``tC?0@x*)AVMs6EinC+VijYM^E)wJxfdn&LdYH9`qO%=vOfC=(2OpmAB8fo6oPW zudb)v^%sY$9basRn{nFQ+>I~C`PjFcb&VlTyA+-zck-y{sivBSV~Gcm-u85ixQuBU z$GZQVFaE4NpQbh)QP_iCESFbB(ftL5|ITl!A6+yn4(cB4p9&ciaDz{vpu{~ z$PC^mPJ|0gTr`BvJPE3VD?&5`6%1M?3sEBIXi!i+C1&)KbVpT9lH3C`U=X9tacsdV z!pwzmA(2v&?3$9ueMA}(_eg40z;l^8Zv>&97na)=^HpFUYN1WKc3(Oc19wV9pW7m$ znv4-c7b`?GcNaj3F(oU=a)`iwj_t8BTGnughos0QtixfDtmHj;&?1^g^EgtsMCRCmUynY#zw#8_mVWl&^b5wfc6rsSb63`$X75hcI?y|PwRt1wkx2ZoRVGTc|f zjYz76YiLFt#hMb7fJB$-5F3+iGfbXFfC>?$>_ude(n^XBg9rmOKTh>_wD2PkSF^vE zOt~LKbUBpMq0{q$ZlFaZQBl{Fx}=3knR1jRY#~SlBXV@B<>~U&6GIpw+F zgpv$7kPWVEku0$tkFh_>(saeboFZvRm;L$1;ZRie5$nZR@p z9E+eKMeGsR0nyYYWaI$|qTybP5>XcDIiQ@N^vnQjaB{cFV~lIJuP|36_xQ^A6+lW) zNfl|v{rVlE02a+t<%w#S<4D;F?=QZ&sLv2Z(wroBkyR4sEoCdUpml(-1ieLKAm4*m zL~>G;RSX*X%{RXt2s3v{?gDGHDrf=|x&N0!Amor@k;8x)NQs%CBO(l963n8K`rwjU z!;c{m4r6K7M_VdSk|6FKaF~L#V{}-ZWFCc3$%CNJ!lLL?fN&;60Xp?AvBKw=5jA|A zJv{D7K#(vEchri*-Z4A;}^+egX+a2xF!k5d|`Yb{4(o!P^izy6po5RT2rI zxcZzJXh6N3`Ni&wv%bBdWfIvf>qU;&N}pYGEHi8&^K87cs2Gp-G}pA8`5NXA%A23) zyT6zFRo^-+h7fVg4iSZ;O7= zWN7N&?w=zBD?(=vlAssPcS&{qD zMVzf@r?POnZSC2-y*peVKfiwV$?enKbN`Z>8?V%l7f7F+cyjy|^p4^Kzb#Gk2qkP*bJ?DFq_vDM?ApDk*8| zB==#6#yBS1ij&^cKme)%1uOi(+Ja&kfCE$_3rHY^1jw0jfH0A+4UPwFWwt5PysW6VD2JQbTl-7$DIvsqf`oULS84o(>n+w6D!R0InTpUp=p zfr0(dmF1$Nq+m9n1zWTh)3(LU9Ei{u`f^z8(t=z`Y$L8fB#oTMYBnP7@3*R*a8OB_7hO49q7xyK zX}Cmzui@ps3Qej6=&miPF2xs=!n{xnDc#fmRU%Vz%#OK17!oyN)(VWF+qvx;lKTKh z^3dcWPEGEm#V~VaOL&ctJC7Ftl1Rb}%2$FRr?Xs3mR?Q<_a)V$wJ>pmc^6!IuV&%h za;_J1%M^%aa6l|07ITqlhFNo;++Ynp10bq+sLwz{%$$%WFx(kO^hd0yNLjKnsi@?n z7!i9nRpuP*lPs*$#gd&uBDf4c30Tw`Nj$Rq;Ii;8Y&MTUq!dVwNU*{b(Ro}&eC6IDR>9u6^4!`yHW#aE z69VG>NsULA0FLAsaRj^s7xI-(pTtYkM9p7CeC6mVXDpeK6R0Ew(HNe>2cjE@h3tS^ z*ZVe*V(fVb8A65xWHxQoI&Qpe!#ekcIp7U0&AUcH1T_arFxq5QLO*!z^@xMlz28@i z!o{edB$Wt>kbnpotzhgpK@1RNagh)uL)4QbVS)q6#mDd#5pLch1rx=RjwSZq4kk4i z0W&tQn48Sf1XGH2=7SFCiw!6)3v)7zm3fZLo#(Arx+mi?{16JbkQ#n}yGG<0Ua0~E zwSwkQX94F(2e2?!A|Bd8Q&7X{2A?dZinb3Q?*&xagh)VBoASMRJln39tAKuS>s>5g zKY0-A(z<>)Ko6`3$y37do-A!lb2-j&HO^5~Rzu`(y;6>^;LkdHaGHMZqW^e)`7h$r zuAF?Uy>WYY_nY>`JKtD5dgH-5sjS#CDL5S3r`NSV=eOU~FaK!!U;owo73t}Mm?ef?zJ2pvm zlGCI3K3~*+;`pK54J9pN=-IycPW$*R{wGydn*ZACi@oVTd=?+{`b)2t;ey>Nq@d5( ze8%tpnar#B;0^!d-{1Vt{=xNkboIC1cw)!nHoY8%^5|~auseP4_Va!J&wl&#%r@`8 z|3Vl2S1-=r%=$C$qrHlG8N3dAiWgO8)#Cxrj=orM=w;R6;soBuQNkeBbh~TQetv#; z{ngcro9DMzSDWkk#q;(8w%_6?t>xz4AE)J>#XCgY#_h9^s*y8YSc#+8+7#ODy7uzZ z!~ADIiWeWvFU)7F!s94*p z1cef_+1M0FN@pf|W=Ibg8h{E;fHEaUzyog0S`!gfRcB`sz01n|z{8e<4bkWa)|uvkFic_SEG@w@A7$$Ea=AQ{jFckSi!7w~In8kx=gomaFUq!9&8MuO z2P>pTtARNLla*sE;<$*K#sjC~R1;2tk+O}rb*x+;-4g~OjND6kWmr5~(aooM5>!D@ z0-~DwMMiO_u^5VhnmyTsa7N@b<%Xk|y~)t=R0{KgGAKLgPL>bOP9vegT&PM5Sc8la z2hU(i!cd8vI>{?i@5d9$mUyI2^o{Tq6|jUnLJlEyKKjH4so~yS+%rP7ZDf2Y0xmk# z)J!L*Ay6%9&;`y(Bhu9>PQrR%d(wutP=uJagdu%p?wIo*=iu;V>UI%Za3Q%;bP zaW~h?!yInC1U%8LnH^mm3DoLoc1$r(u7}BYlaR=&f}ClRB??rMNEvi_k{n*)GUTpw zSr#W;rNUJoVj;76c+{9~V|N?LTN@rH~tmH8q9$~)qy0Z|A;tDOI_b7Hq zCmf;x3rB(ibS4ux=?;$^ba%MB6?$)UhF z=59UZO>D1Gip`ztOdbhu!RkB4okWDFPy4#rjP0o3eCr9YH|{OmBZ6ckhDao37@`>t z!5v2PZ6vFW7GkQF)RCPIG^)Z9n+K|EiwMF41}G#kO~Sp%!DV#0w>jHA(IcQDbHXV8 zP~uf-<~o@!CSO;W&9j+@SYT^y8&ir@P!5syk(H8F6E(3Ql}JO)Qo9rsLpC5B1=3Rv zjE!`w#EUEj*2n@N3}NtTc1|{rp%q1APN+)Fk|$s^_sEWpfiCGS<ALG4 z=<>myb+5-s*mOC27+P{uFIPN%E?-H?85d7beqQ@-Ec=~Z$XAbwIQrCo)J{(}{dWwn z4BhD)C+myutyP;I)T5xeEU#bOeEI3~H)Zq9^~tYI>;KC<|6G6fcluZNc=x89-Tq;t}XrG`1$J?ej)wRGraRBeEbU1w{W@&D`A<3#FIapZ-3|T zy*J}WKb?N|AIMMry^e2`AAi@stB(h z=IZ+9_G;I5&FU)Ztd-VgZ+Y43I%X{Td0n2D^jVVJ=aZgIbd=IU!m+!Yp8a?+KmXz4 z@XWvEzf)LDzF_;1+5M&6NiWSwAlO$tQaGm3#=UtK)5=o^`5@b*g)Gbj| z2Nf!)dQ5W2I3pi1-2pF2O;SV=5KrrfbD)|Z7~6t7MGq{YJ!A<%xFC`V+r1DR64bz8 zv2<`YIqt)0%mIn;W#}o+B{V3joX$CYQ}s(wo!J+#1@< zH0w}`EEiamZjcaWm);63>zMK}=UcR^@JE{8N}VaHNDn)N!6J(CdqqVdF2*Cbb*J0948{k|PWMI=E3XDNqB zJ9J5+6A4k*vWmC}K{SIk=hS3$Ou#Mcm9|{^e$gNDu**vqfy_0rstD0Z2qMBRsZUu8 z`~;z43Ja&11WXitNvDe#g1HLK)ViJ_Dqs&}pAL4g+B_gTvm5sm=mH^$NgOb0({Ry| zaYVfIcxts5UTDyRAxE=~0T5-Hf@AbEWft90w<=j>DJe(oeF{i}N|j^Q;jI8j*!^bl*C{0OO9n(^g8qT!^Hc zBr9+qG(_XVaS8%bL5pb4FpGwOlXRG|IxXNb8)s)_*YbdtWX}L1MmtUh6EUUV|6OgI zBj7?GBMz`GI9Osu=V?jlDLG_-u7y3_J(}?lz7dmsh99A8@Z#>X<_oX|=oy3(u>4m;A+%`MvH{W|3KADWortp1)fQ2Dy zlEhoFIqJ^FySnqXgZ1Vop~M|LWojv4B12|wSxpR84{|95-2eD zFmog4#8G3W6hfHx)=kw=BkZ`fabqfxvXc>+=>&NVFp`9KqD6zuA?#AW%5I(1AqWXa zb(u^Y;ozA;|^<{ajmA~@p z>PHg)sE)5Z)(=k0?QZzHB;>9V(X|D71Y@e@QltZgn&AUHt|KuOF zH%%Ws%J)sjn&pkY?6#ZVeEy;ydT!3-ctp2(#nSPzJ9obOnxw~IA4a&~@&|JHn=(&_ z^7{72&+`A~*N1P--M{mTs}J4(SHE+3uk?TE8>cUA_v`J&xLEY|$oKo_4=x|Sv!p)0 zDCO1HF?_>TuiAFYkFIsB(f4rxKHA{C;*kmm-;|bi(qCpkxK`VaH!rt$PwVc-w!eO{ z-G6oc;_CAGWj)lJgG~E6?dN%Hqt!{_D*e1ZpU)gAI?=vgVbB9*2NlnQZFZkLyZX~l z{O}`4x0}1&jFyt+Fo9=^CK5B%1P(g2%Xuh)Ff&iy)M7NPL3*NFma19~ER$%WByuU~ z<&5g!tz1B~zl&%JWxN68LfC0!K&$w9Cr za!|<%)zBn!>8I|Vbme!9qogE~;BeAHz#xu{xdfCMz6J(FLMPx1G*BB;WlE_ONda-t z)x+B{;y{CkvPG`p2~BA(J(80eb{uP)9aEFJHkbsWN_Z~IqC*m;6mRz|D1$gDl2gL4 z=!TOLvZ@{`C>kl{oJuNv?ySUq;c}6deZeSFNe{7zu$bq=)A8=hIe}ePu_zY80K$!# zTDrx9{-l~ub8R@jOncpPyw&?&G; zO;)NYF(M?aSu>M?x{%mIt^s9U3X^InGKeI&do#-wVwh&`4rFvC7V~kO>p^Q8=4|`j zVRs$$mUJ51I9o_Y>erR)M$aC}4fB*o9Uw)!U>|T;RcF_bT2PsCpNDmp~Vz(v+bdDNww1VK%om zV@^8uWhNR_gf1AvTOfQ2OdC%dD;cst&W6Fdb<`DI*pyb1r({lVne*{bx{9Xx_#yGT)8ZleZ>Lp_dB*TI zKj-hg@Gm|YAFt58QC|N?_2NG|bdNHArhh1i zgs1&@wb*~-IrWp}4>v=1E#K*I{%U#SvU~7@^!Qcamry!fO&l0|iI0DC{_MXvT)d9< zm%IMg)7SqVR=>y>{}Xxdcj6m=kN!>n-rt^nu_4vr4@~rS{MiS1<>wZ!>0~Qn^h-YIyPW8B(_EXzgSeUYE5-n-&Fs9r&Ph*6qv95NWwn)R%&FM$StLvvv=g+_{ zq2F2TB0Rw*91$cLA!0-3GCagB^^7IN=9nuKA-rFz7s7?1BIIIBtevJnHaQ_5h$>CQ z*3^~ip7{oyxmeQ*H&GEOiY!#oEYT58803CYMq89t33IPrTW-FJSO(l-h!p6NGw+ub zm;|$MVuGPX9FZGS;{6;=L?bjc%8W2tiZ-|nvzM_dKv^_I6K1doODBC-#2jXb4;odXJV0QOJ-Oy&!kueG+(% zFd&2ri6wDu^;wf8d7)a83`G(U0h82nF|ojWfNn5HFih*QMvZyM(<<-a8@l!=WuDW} zvZrB{`^OCj3rld4nc7P#Ip^F(iRh`64w^(nkg}GPp;Fno-2+=K&~Cs>@vJ&1&1*PY zL`5(}00;pM1Q?;>InNmJ*zTM|K&@67C3-!I>N?Eer7zm()zJHaCo7eQql^NQ)35 zNRP&dKx>d@nI-CYA)lJOgw$^Sg+xH(Je}968`wQ8YUGf})OS z_nUfArs$5^#5^o1J~Oe6TuSJ`j`B-1$;) znh~CAI3o>+4ffAMi>G8(@^;a6Ij_5+$@JsZ?OU&}2jdcX8ImSKIdgyqs5k{*}kWpME+1ufMl+XA*PkqPNZmR75%Pe`=ltEz7=66+z(4x%@E`tQ`^KyN2MH}`n-0XaPUA2lF^GU5Tu7tg{pll z&v9yK!O0o0h^b1ez9dfN91prN9ih%bbl$@&}r94XEIc~w&{!35EvkiuAz?pfgovAQS?bv2?l^Fm=Q39 zk7JwM_8QiQuagWN7J)?o0*H3~&@VNGt5v7ZLMH;vql(W3p?NR2PxJEui!(b75sDDf zrKBg6b%dGK=5?CKvE9jZrMX2BvM?|2IYb%|g@V1Hi#~keW+F}886zgxep%C6bIz2K zQU^NV@SMFk?x(LvL+G-xn`fz2qfe_vcRH)Msw_+C)4I#&>kcxo^~V~cSrninvgDj7 zIfVv#ks%Xgut@He$nYdWq=@9qh#s*Zg_3=i2qku9_k z14$!8SJKV`(E=vPB}}qtmlB*23DLkgK+$L=s(6j41gS2CK%z9n93-MJoeIuHHi(y) z??-0yy<^yCV>5;K+J!GrO;L$1IG74d zl>&E|QzpqfwgU~I{$#LS4zLZ;L{zrR<*Z;%SS`9!@}X*#b|^API!oPGq94H>%Jm&bt`yznRt!v{PW}8kB`4|iXS{`U;CZ<_HX0|Z=W9D@7_u{ zTXvgUJ$zapzKQ-XmH7|a%m3@Rdk@`T&!?ZH)8Ep5EyL?P@5X(%XUDnZS1z~y#r2;* z)~E0HU;93O_P5ijayQlPZOC8mpI){0FK@5b+nd+-uZ>t8mS3K9@`nDbx^-^#ywi?^TVSI{S<;UfI)V24|yZG88_wt;ETag&Ej$$gXuvFu_>j1$Hw zv$RR)8-M=g_T!h=FMhnac|N^-<~Kh&-j18=wA=5-dCc?Nx5?>_No7{)$DDWF&`CL= zhM;MV)?-e5_PKm?b$j{j>HOuaC9!iuHL$jBwt-lx2GIyLWi4Q3gp8(hGjJjs+$By` z9;moz6IF^p#FZ{h)Fl`5f-s4Rum}l+ge6V^bIE9lvnMu9MXZy!UyPn9N8~MH3kaoX zLKm^1CwHlv$TwanqNw*e*#-k zYs7>YDF>}D7t3eMBt#dwQaexZ1j~%@a3Ug1hU2sv=L)M5qol3QOXMd~veHEoSX@yn zS~GWZkqD*q)HSt-rI^Jit?zPMsLKpB_MWqyo%sGiz@i;}wmIC;0X-u(l$jCD;ewRn zoOPx}A6%gG6g4wJIz+ZeCJT>sAX9ot13X0vhsQlJBWbvXPGJZ0gyhY4?WGMM{JHKay!pWIqCSaXA$`aPJK9(1SApamJj;B{qE zCmWl>nmCH%1ahvjRQ93+m$lW=ag4l|lzJ&0TUXj5xd0*@0)3XSR-#8lW?s-dMaz~My=Z6`Dv}gQG%+XjoNHERl3)i-a5~9Z zqSAbCF*;$HCSA%yh6mbxznN?rtq24uVoiA-N=m^XY1tj+%O1y~2}|xGDQ*-DZ5A_a6;AyI!9^zx}HJwclO8 zdrbe`xBKmb`n_)d`m+CSKHJFcXOF+!Ep|V=-Dh3?!pWmP>4)&ggS`7V`HIyej^GYY z|D0d^K97E5uRTBRy6^qqc1OKJt8!^+$*%AFtXO$)6q!=#rp3wl z{q08|UVd*^=j*g6kiJEP6K0qVGoXyjiBd(pqD{^S4bO9Qh7d6c5o(f!<|3FR%}E@^ z;LT-jbm=IraMdtjWoSfkS*V<3+@BDYpfG6^!Gc)aTWhJHD8mr}=~cS~SwPnaW($c> zHRhC=1d=Fm-;9SLs*NeOSyL&iF7CflYFG`ph-nF?xbM->M%6jHfi(~+$cRKr=_L2+ z+Ky>XJwmCwNPBTZIM5@n7>Iy}iVRuTDp>-w5S@`G04kapVj&6a$Qm|=O?x|D)d-J7 z9$XN*o+KY4q)Ix%4s45ox#yw8M&Yx`Y=Bn6&T|N3v))h#MhA3+duBH}f1&e1C8`C#^pCRWEbE%uZR!H7f} z2`ZwP)rcmd;*vd51cPLPl)gJ#FOI})xoh3Z;aVU)Bt)VqXVNFg40`IByVJ$_Ys)x| zmaL_O$Tk+83&M=e!{BY!@yHRTCb>V;N3SGb&_o2H06bE)Ia(F7o#VzlQ5LF+MHwyH z9C6d8=O(U9g6d*`IcD4MV{;R}51M1sc;gxr5%syn=m*ptiD=1dSPPE{+)A@7xqy+~ z8RlrtkN{@UrWO(I?GACpFr_Bba19sp+}I1VXdk)^z!@P8BymylqOWt%{Qe~nR5IA5 zSXiB-9o?#0MUAMA+Ugu==B=TMwiFHr%~37x?OQc$ii)X)d5ilh04PL}lvJJ4P>*hN zxJvGGtc$La*2QvMMI|c@y-V+dW8M6!?mwGVkOnl?NoL)KU18emH(&jLCQu?^?vr4u zVQpc4;xKqc^feZ<2E#QW(@VaG22{!|iUhZYp4d>~+<~**Jp;mMOYNXNwkEA@o@dCG9T7~dH-Z>veyvfUK8*L{O4&(4q zi*ed?#JpUc{uaOSpV=#~jW7TD=AEVJu^(U6&%c(&G%nKgQKz>i?<&q8=hJmpj_IsJ zeieFxmr5zDQOe(UcSe;eDsg?BnUoYnH-U*hElkC4B0`suC8 z_uJj+`t0@9FC#809y~&P4G7=edD-!1BOm_$@%q2ooW2J6Z z#Xpv37wr$<+`hX6-dz6hQ~GZl-~D-{|8aSw`ufN6@J;AWhNC$D@lF-a-=iEHNVOy>oVHcDrY?a8A=P9GnSmL! zl&uUA3>S^2QL9IDRP!1^{3L5n()A_xy=G{Mq$!!o9yo$-9xd*@+As_ALG&c$thA(& zQ*Lu95iL|P_Sq2oa~iDuWis#Ll8E%nCnV0 zW{zhpukaOZhuOv&Ho0spZV?xSuXiGVM24VhR7R8eO43snMeC!rh!#dbIR(VvbBK8z z1G~65_wm)RiFwjx7jxkuQVK0g#;NmQaTo1U)90lY>CdHAohPE9&S*Ex*Mb6FMve%C zH?r~G^=maa8>ebVS2ATytv_q)(@oEtB$?8O5KItjR;w^Y>T`DQ4dMvDe?!U|)0tSSR6Krxs5#1Eh|qRkd&L5F&JjqQDwJlDC+KDBvYYlC^uxoK(ixknG-l^pMs zP9i1;BN$e#9vh{Oxz!uuQet2_lcY(s=qxx=o{4@gNLiLji-^D+c8uvp_*`H~bCDZK zFWE_#(qeF7h;LOk)I|1SVG%91@N3pgtQqcNUcwu~5D_5_ZZHpX)GNcY<`U6|r9i$X zt%_z9mAn!^WkkTDh*@d22-9e0;SrJ`1%%Ne(9FD5cW>6^woEOdQRgI6HiI}&l}#i7 z(oERI?I-X=^$>HX8QkD5&9z2WYS9)kn^$vl@2#z@ts)kNQLq!%+2-0LT5?^RUZDFd z=kG7DP@I8+q>``Ip2_~8-+b+DW}+yFDX=%ODnxk99wTT#tv1fwsU5OJrgQ+4M^uSM z>m!!1oLb5#!7ju-Cc_TCgCvPQ1qRyS6JS9|g6LQl$IcKYo)0G1P4;qM7uI7Z5~YM_ z%6;bAF__I^bI?rP97-mrIFmxNiu6f_B3Y<$&#&?w>PAE(iUF)4sY;r&Qbb(TSs-in zr{c`X3Z)O}w6&r_w z*V@&a@0BO7u1`fCo>CvPEHNGYZs*4)k43HyyFWSJEu@^x%SeHR_XD1Ego~>x++km} z!_|I!`TX_=U)f!oF^+(@3d_UdJTDy|97eqrfD!}+jy@ZmWDh4qmq%2+PRC>xnbe;6b*g^sk(4J|* zXc4nR!AuB4RFq1%(1j^!*Cq6{Ow1x1`Vk1^U>A&}np791TX!*rZz8~~;z!0_SVVe} z(xe}Q4Ghb(jCCs~@1S^`UG_Lw6Z0~AsgC7*Vop>ED_kJvbF%*=7W ztCPqq>?E)I_36SD&S26mQ5vPu_RVhQ5HJ&yhnaYi$Rb%aBbQVP`{>qI;$(F=m*%jF zu5j7SW;UBxH_t;Wyq82&NtN0$FNH2LYs?Wx*NAkV;fvX5 zzH`5+_OcR%rKh<@^9V8kZ|*hByyc@{)^w=0HOsymOo)YPx?3#k2_K-IHultoG#3G7 zso!}2EhG(=$bf}Fgk;o+gGVFDZgkm2I#Nd{BeI)UScSFb7E6m$X95-48+#un_kF|; zY0K10ti+5Q!Tab1W-vWQI|Rg3z1%>*5;fH<=?Lsya*M&)2Un2>^W;$>GqTg1jX5(H zK*N)|6iY*30Tl#6i6FR9z00ML15z-1q{%tC3Phn@a1l6dc3!QKlVpL4FKT0TQAMJ7 zOJ@DT<0MEYyqHG=L|qhJ_=?9XA3whP;=yuw=X8;lPxRvPQEV4+>U@F@hUR4#?It9L zFR-JGGyP-hzHd*n@qPTKU*GuczE<*Zr)j+>l+W2FUI!K=0%6} zWU+pkAATXHtCU_zeLY+sHr*NQajsAI(+_U1o?!Y~IsFX7$Nh8f?O?+P(7%zFyWQ|- zS8~eF-|k+$!TX>0*ZI`C{@V?2tlNLEabHS+2? zUdOS$`55Ut#csywPj2V;;s5RKXaC%O`XBOjTRt1QuQj|qhlpr`iLDA}sxfEnmIcc!gXGh$oGr?3H($Rfho_(IzqmeZZ4<1ez3Z-q3CX&*@3Du$Yorp@d{b6toiP9P7q&%LkjU##7tw3EJe|00?8qc2<%^`{6%7jicAx=DFl)o)mSY@tK;Ntc33bparHp> zT3*bMBccnJXfIv9YWbn?Egf3;(Pf(FaY90L923JukFbh%VsYWoB1ZS;RzLCM!s8Xw z&M|m*-r^+IfI+@hYgQs=1 zRlTQRIs+1*0KuWg%HqowA2v@#7JUT7472CvJrD%;(2Z!$(wDUKIJMI1T<5xFT&uAN zdOrYD$X)Q7A|-Vy9b-Y`P03#J~richQ3~-7tWtMZq1J~TFHy4jq6K97pxeai=S2ByJCgdc!l%lER z9$Yz$m;^A7Dx|3NC=xxic|WItQdEJ)~ z?hHyqRANpJMD+b(w><1y$jl5wIBLUO`(@Z^XpI=dk0v{p`&|fuCHhWdqw$jZj8T;9 zfFF<8lMcn;aje|CEi)LOSCl`V2W7kH@@*0 zF+)bwS(_!P%r*SrzH2cz64w$4_buHb={q{h^!a0Ns$ne3rh6gm@A{0$SF3YSE;*Q?A zFk3-ZYzikGaHmW;x55*oQ!t`y;pPZarIKU_g5ILdeh{w-N5{te=xL@`a*pJd#P2tv z<7gZ$_J|##DLi3h>@|*evwgW(pRUq_9_JtQuZBg0RVNOTw$4M;V~jOI5dxV72c~0t z2x*GMJdlIr3BBsF>D9FP zJzS&9ucyWS_V)64DO28lx$L9=i|?GhJhzX^-FcrMw_&PRZT0l|w)@>5o~*U|>u;TA z_J4lICoDg^SnV`z#d#VRYy8QF@qhSToE84!*W{BQkN@+3zW+|^|IX>z&9?pK%@Y(HR6K~n- zrfsj%m2`ATCQ?N;m_m9fC(;9AfsDo4SI6Du%jvVv=Py2;KASA8F4a?^x`H_zrf8%J zGboA#K}cvwfl?(aUD-$mQbTMI2WFxpg8>gavQ1L2HiMk5| z<`iD7jSUI91RSV@BWCxz>e~iFl7w4Gi=L4cWRPkNb#7biuMh{Qi#X{NMeYeFAv_f> z$S&ofGfG6TM_z`y+vJ=*#@N4{UfgVt-En&|r6PqXS|pGI4+~->cGNz1C!Gd#2F-Gt zUD%ft%gj|)!?L0oQe%epz2=p4SrR2{($4c>QH`_B<5ces)eQnn|=# z5zSMThssrM$7n}yyJ@@|sYI_xVo7xZr+|cb$kEZPinpc9nMLmi4|qr*c$md3t*v}L zItU`8VU=PcbM9WsC`4~1+03?(%rZr(T{3zAx^EUO zUkv$dDKxd3!VBZx0n4CJk&7Z5TX>zvxwRQhg;|HLUFZ5>~Z-ow{)3&H&YN5~M8A{O+u&=Wl81-o;>I#k1rW*0EGK2~Xf?P%UBOoBCsGNIpocc3 z)__@0EzaQO;qpT-%25WLS@BzkJ=0D54W&PR_#TK_;LjP$0 z;vF3STG0-#wG?f9d2@BLLcDo;_rr|esP-04{$hIX4kw?oUtoR+=V8X?`h>U3F8}U+ z_{Z0Wuk-kCcE9+~@xgzZ;xxT@58t}P=@8fq=qKXXnk0a(9BFEhB7n}AS0+G6p^D)5!vsBA_qZDN$$m9j4D1U zn{x6vnpeU^E2UA~=^i9WU7DUoZeT?!<{7OfPO2dcVIxM-hDg*!;z|-NBv=R`D>;c& zgdr86kC-VVOQV>mkw@WP;;4}lx{T3qm}5y^h*rT-WzKdewnN$nl0?rCporuIQ84!? z-F>O!D)Ff{SC*6=4Cp{V71CW?7J(cb5KFWOIIv_o;d)~1h%x!T%r}%f%qQAE)XZ#6 zjFz)KTB0s#JMad2!t@A>+1mbIpo@JGT`(=-E3jG}!<$EoG6>IDav(e_A|Va#%fO5_ zq29qRaTL)kGSUw)1mH1Q3wY<~0*TV4yjtYEw4-nY?wD>R4<*(Kxf@bnM#dNsf$F{a zOrMv~2bBI^REH3FxHX#%a$k_7pGn=N)(&lVTgNWi2`rIGL>ZAVOKR*=q`~{OaH--! z2UT4qj=-%p)1EBNyjd%Tp(SG)^&}XB=Cj1LB%3i!O88F_6(J>(0MS~DFgI9Z2?=H< zl`J9X#Wca2i@BD2MohxgC5rUaoG6`YP9ow07n;%%+#HQ+);Bo|u7Gv4(?~mO*JzrN z)6vorMR%SyEvG82Q_D3o5L2_}u_z$Xz!crGXrgK-I8g4FOFilj(#zkVNz&}nYF zqhOxt-U1Q2H{Lmm#zJcrcI+`as*B!-O3c)zKIhnLYz3JzFfWpvbV89VsTWt&#MH4{ zC0}KkC_9!3Jcho2J>@7}?t4klLLiB17NI1$415V+t6xYXJpl{n+BG3SS(7KEi@KI` zZ?8C#Pm|C4=DYX$Zi%mV71%kg&7Nv*Kgm z&YYU4v2ouSkB9@}=&^B5BpJ>9XuM+Fs*nkcxh9`0Vm5FOZejQN`dyv(RY$QYaqckn zb#{Z#2~`oZ=w51tGbAJT;p5EhNTo7)izX1!q{*7%ATG+B44NXLB~*##MoVbPncR3b~QsFOKol8caKfD|ZlSJG1A+3xt?9hqFidwMPr0_{bD|l;q17{P;t( z#q{=T_4d>H|N6bdTU`9bS075**X3nbvfVB(n11<5|H-qfU;oojUy0MNmp2c0^~Z7h z_JjN=4Y6exunr(%-c0rScGtapzMSnkz50w}_u=(}2c%YWnD$JgHeKlAo$ujKb0EtAXR$CPhIe&n_J^U0&)Vke&+kH67v-HTi< zbR)g+)YMUhu6=)I=*<`Zgh&S6;ee6ebPR=q9JGs*JP<{ zN-8x)rer8s0uj*+Avxs^3#JperKl;`h>8&L`-x@-T1XWYQ4wJh(PSxE1Y!m~N+XV; z7EauQZbgMjR3xgzflfpgoCwe18;{E{l1T{3A|@~;CDa2WrIHj?VJF&4h*A@V%&w4g zlcV;EtZXV&PbJcjBKim{$OQsIDpf_bl(HCl5Q=bB6hWUL85U}RN=y>h?l%s(N5-=^ z#2x*XY^2eqdvZw8+S4G6bgqlDWI?eIx9T>NL5&0JjGQ46ZE|yj!V+^~Nh%kmhDg7W!48jyiT;03{AP|s61sMJZJo3nJ0}uQM)XW0|wt)f+cwj(qGq8YyU_+KH z3uH+ukrG9U6v@nF=54=wzvg_Wdz*;Zd#&ZcNqK&7fVYWwW3Tl*zuy|ZrR*M+CL>0c zEr|-BL}k=W6I7z>xR&lcvaQm)bq=60kw<3*0O0>cpQv?IKp%jwC7 z&yqq+;^pJ9)A6!Q8c$AdsRq;(nL+ibN5@m z**>ugxjc_KKeZ;B>{sv?rbplwRZ|Q5jrbcRqggD}%U%cg&A99?VbAQUcRRH=Qnb|X z3>bH~c!BnuHuu-}D=h9ue);30|Es&}k03wX{NnGFkN<(;Lw)xbj*}wR` zB7d*n{1y2B#Qyw``Uii%Ex)0k{`c%}{p0)#|Al`0d*hdXtNi+k@1E@0i&CD+_WkhR z2t1D?@pOTo_~o(x@w@et0sVvF^Vj3kKekD7^Cb5l0Ex3DjTCDVM)(+NlU?CqOB zdHs9mn>&5Oc04Y5ykB^G9?J1{f8QBwvS~SYu3v|K9st#Jb5K5%rM00S#(22Hk~eSP zy!z^8UyP700x5#9^bt8i)5U`!3N2lvQ#7)Alwg5mGN8_E;ze>KE&@*@8e&OgAW}lI zQyiX2rYMUk?Xmtk6az|f6`a|-b7}H(Fc6S56^u$@fN?UruWRYDS>-AC0`WTf7unVM z=u0tD6;?AdTd)HGHKGc~;OKnJX$+8xDy{}CRiqFqre3HeOLRnVgWT{j@ht`#Y(i|o zY%M$rePReCB0bpB7RWj6Ci50qBR1jAT-9!w@0k7w=@go9^<35>Eo=H!^h z7*rEL2?V+{XWrc~3>={dnBZW?mrca7G zw40PFB@&DKq;!}AFPI4Kk*99-96orBBBHsooxT6kM zw$(CQ^SD8OEq%M4o?NAkWw13UK}Dtq@8NfPs)ZZnPH?HR40@WXQvzCwmRgJCAmbP8 zKVsjdy@<_a__Nrr?d`esUG$3tfPicpE}D7obBFb?$Qm(bYyC8@Kx>TEk^|KwA?fs0 zdZ!4b%24F0#P+Co_-;MGk>2P%;=1t+m;EpNO4! z(`4^!TR=QgMOZT7NEd5uSiNLR=-As7kxg2Beozx?dm z&=Iz0Zc|E9P$+S(!(jq74IC`D>Je}kg$OB+3tDI(?tnXTs2Hj*(VGJc;)EpvL#c6;Zg+D<9kM+-%3I%aGLQ-Pu;i&dal4 z3DICU+*wI8b6}<2i=GX2!?JCKUV+pxw0S1Nr`Ct%vGW`8NZHLocoVMXCQv|_1v+Hi zq%$=c|9`hCHA91{qJwBCQA>2 ziK|lIx9R1){QkpXNBh9WWp4Dgd8jvU>ex_#OuF;y*XuujHJ1VJU&(T~JFRca)6FW+ z2HFolu{U%6zy9^|!8HCifAHj+)8QZAz4A@X+?A8QxZEu7O+TvkVq?GhbXt^(B@2m>{Y8Rv`@i6kT+H$eiIq*ByiVwI2qGRh`(pCFLQp|P&6;9~+M#EWpW zM-;!icU@c9sKcg}Y&A%Xg1*T^@%iZM z3^b8$IqJTBW%R+D$6@hT=k~Mcuq2af+L~lT_T_Dx zUo}c_0wSg7{Azvm}g5QTiJpSO-#tlqLJgm-Bi)##sjCQ0bbzh|v`2 z4Si{d2ha?ngsxdvQnVU&nKdY(lKs&dJ7=t!0g9kZMYhcF^nQxCNq1opp;amAD}6x~ zUI?~;B2M5fe3SB=fVb{(So*tT+2ZnjYo7B7$HQ{CpT!^}HBtjyBi87>wOG93xB;h# z0-V;oJJWYHo?>=iTPQ_g!a5+QlmV#{Dn(SOL4<@4im9YQh@z|o*i$aqHJbp^87=P+ zuM<6~fC{Q%7-h4G2(Fzt9fx~d5EDa#N>G3BtzV+-fv1SlnN67u;)nEG&|JslF4rq} z@91Hw5G%qY5XqKumvJ8$fFaQXNhG9;LNO`~HM2M~G#Bw%au0uoZlaI&%8~WM!e3A| zWG4pEfu^x&w3;?$a&|w)lf-+-n6b84glFSZmk+&Y0x*|!qX{)PtK;138Qqyhy1{Fg z!CN$s#Sv-5kYPj?Wc5H~2p5-gHu_pzgXU6z0WziU5Xcsf2qFm)Km;&@GLy*~J|XXW z`SN()Y^V3e%dOSRi*3b)jMS?) zb^n!K!F%oW_Oi8$S982tzP{4sgR);`k6{&T?~m~b&VOTg`mfvb|MPPH1OD{CjX!sX z&D-&hQs=|+-X$)6P{qWPm*Sq1Pe~=&jA9?k(lwZ;^rasGYpWFaWJ#v`n9}sf?dbxh{vzyyLxV<|) zyyp7(mu>Tt*7_WBTlH|=u zr}d#l1V?2l$YdZrGO#5s=OY55%43Ahr`DNKg|NeL~+NpJ#jvqCgXp05UTWtkEp;s`B|3p0P%%xDt!RgY_cA z7UMc?&-?uq{6%5~mlOpgLxa}FskdE-!&jhWRJ0TFkQcXYeHZPMwmqC!~c6!oxN z3$$XN!q6;*WuPP&w&uEUtrAmaX*ooQyLKs=ndil|IlEaOY?HYQVKnKjgblD~E+2T| zd|Xb@QSHKnqvYk5ZI4kgEBc1HA#R#~-FuKCV;!c;y$l0_+UPU1Yi0$NmJq}c`h3l& zo+WYsCPc};LtX@D#zOtPiWrOFzZ7$>U3DJP$Tt+7$sKB$7RJek`Oc@z7#O zY?Be$(=#!q4H=2PCSX5=JT8WWy zA^J=%Ut9mQu7l^rDvUt(=-s0wOx1=#hH9O;QXeCwE!k116s=l?gI3BUQOvfAAs41f zb6I;wtdeW62c>&ysgHE8*3;*k_4Lgg1=x!T(vsdIk{6OMnA)>7zYTw#{ygP7x$V(r z_;utri1#eNFXLI4o8jgjcAGky?WCqz47y}2k%0`+O|^@vrB)qqPh8VO3;J$7pZn?> zv>1ly`|#>vPNk^sSb;Ibqcooe*=~g`F>C34XnKjbB1MuGk!Xq6BA-(=u?ZHzB-oI9 z$lCNA+aYq#HdPS~GgzVMuYCA1wWF-yvEjIM&`onfJWbi=qsfCO?@~AS>a6cHWly)#^6L>ps}zR6%NP0mb6T97MM zyqZ_c(N0}(b~`!?%Bbdo=76U>Hst$)))+2yfl5}opF@A(sA)o zeP1v0{g3=^*2AgjQ{7#bG#lP4JPUp-nc0UXQ(iokYNaj3s(x@$*RGnFuljn?Uf#Fg z`g(O;K6d?$_auzSun*hQIyo{g0dfm+ubW zuESq17k}_@{NJ?eXXoMDUtV(V{H59cs@nT?1V8x&@^8RC&f|T&dE@h{PrTeeJos|+ z`OEsd|86@4Km37ieqHy^DW17z8KD>K2Pto)&6G3Vyu5z*)i*yq-kkLIbievnWUTv( zhRfT#?bkASG5n)(WIrY{ zg~7FXb0&?NGvh#cuH`$Wr26AA1D5IR9@p@15KTR_uS>@{MKM%;4t<8299&QxrJHFH zWvWW4qgbIN3lMOmyI@gDARF{ZTQocPNGB3+;maDtcJxau-q78MiZB%`IsrqB=l!$O zlQu-Fo^z@vm_$almdhHkbc7>FNm!TG;>6xE8mYTtRtBpcK@B`g>r39P^h zCrM_6WU!y3orXKjw{=s7Cr>sm5#dC$Va-di$pjD}NZlx(t6WG_ijX2pN}j65$bD?N=1ui*qMP#{~jL0eHB2dk4**%YxZL+4rI8w=wqSn!CVL8zc> zOI;{bd8AzAjQN{-){UsCJO=AL6PiLK*qX!=$SA;Ndc}H9Uzv^|aVc#O9O|@8(99uq zr&`IZ8;rd~Yv$V4YBoWye!SL`1Pq*GnnWph_D|KD; zT=v^?b%pcNZjTGrMb5$c!5q*RNO^HV2T71k_n^~ zO{C(?004jhNkl|__%!g;kr^%d5cw`2s}VRd&&plu(;VN0zl`)GFrDFN zX{BmW3xvv}AjPUv5@+O$0K<@{z+HBMRJxmlaiVQFW?>(yl0`E0k&cZ7yJ)ALTnU_jZACU)$Hq`s2e_ zFYMx%x4&}c#W$;t?f9bYu8>cs^(Nl_?4f)I@wYd6>(jqH9o{eT7stQw6MXW|?7O?^ z^j?v{+rHmE#TQTHy-!pBo&6iZQO+N45#QR*UzE-7`0^dx{Z;dK?USFiEcrXue%SD# zPVc^)>L1uAzl7a?ChPk;ESP>F_}|L-I39Y|*RgzoKXkum+b%f7@F)Ln^X#9Miyy@B zpXK-*edTryo^x}-{#+m4`ut_@=leJ3>${iV{K=QE|A}7rCvW-H^}9cQBNvDENxRy% zdBh2YQ4%^)uAc|1(l-Nq@2Y{ zK^@pjZwy5WJW~bMC^J+#%xP*xErhE)GVU{UZaCh$y$hxTT|AhAAiBsw^=OS&M8{U8 zkD#T99F%v;@7VCG`h1D?;42=#!#!5VEB3FLK?(w3F%Fw)zb(%wS24Jvs}x9K5lyO! zS+z|8kU+YmXU}Y@QpCzC<*aovbb|v95%%4QspmVPR7 zfYr4ZZ|;;ZMn^=>Jx<<%MG|BY>FG;48|#{cn$Q>>45voIMf&9QRhuj>=d%D+wj0N; zk2sz3+^?j*UoI4jqp>K5_$K4C>?SfvX`-$MbAhu*ga-sta+2I)1?IF{%B|kLH%{M` zi{9GFPj`qnTm^Nab!MY5Fy;tK2OgvD!{NL+%q{aQULptAVTozwJ>rH@1A}WKg&7e& z1B?*#KsuWDbF|*hY&lW(!e{XJAwz>k3r1_$oagOI!rfVh-4l`2{vCqo;}XGJe0Cv}k_r6#q*gIaT(dX^Ls zl6ptIR;b#f*3A}^*MhJ0Jb=5DZEshr7g19k64hxP8k>gJU}kj~fJf6`0W~C7%`TCG zM;cSQpqE-{JLxuzQ45tK0pRBpRM0JQ$ZENvRHkED69FSCseisrF9DlUklxxo!R` z?}cXMTkT&m$(TToOu&E*a|<8fA_N8LnYl2!CL%IuSWAYaF`iI=<=fvP9?RBMxHSx- zicokW*6a=iG-1t$>^Z0w!xY#BGQv|K3W-J5qeN<<3XqD>gr(2~6)6OmIcCP}xbr;a zBTGGj+%PNQnlP!MjJ~8Za{!e=Ni8<28jzCEOt_|Q)22tk61pKY$xwrt6sRmFi}sP& zkQ|iT+{$<%*;(f74Y>jX$4eqJ9A&Ue8+6G>l+?>r~XQpyd> zb&Qw%;JJMH=Kddief{1v{N=0tLmVE;-PE@6@`=>-Tb~?m9@9gMXX9|aZg&sm{mpd#cDa6ziM@FH>7U5o|EKms?eG5j_`09}-(SAl z+4hs&dt-AMABMr!n_)V(A8h)6jrKpgpZ|hPe|PtEyt9`#`QH2TOYar$@D^pv?IsU5 z`O|Lz;(J%TKem7I#hfz!mCMUmN*7#QWkvQgZXB@mDIvO9b?*IkT<@0aH}Agr;!j@R z{hRA!e|uwZ4xhjI;;YxIwFi;KAIZBVXk*ojol2KHkdVV9`Yo%Ul;w1D`}Uj9`01zh zCu{3Vn~Ii63PX(=rVtfCJ8(4V1>1^!MKo*c&mCDCaIe}O^O{VP#}AfBH(z~S5=}^(={6ht(P@wZOIU&{bV;gHLntaL5EJSSda16Mkwe79 zz}9oqq)Mp;BA}KiJ5&^fDRV5jLzNtpB2tx?mTK)j`qG0CL8wEcIcJJ(Q3i0(9EYKA zug<&FaP58764nQDlr9PqR zoTVQlEQz4mG)=oiw(fOdZ63uzWCWPrqA%e$r{(p*Au<-5gnN-SdCWYlxLp`B7V`mZ zOhHsfE*^73Q*$#pFM5bY+Sx?9SbK=`17?w8F`hdbP{(1jnds>ZT!@aOYN@3wQX?1) zkfN%B?Z^vcW_G$=pxRc_dYnTY3PPlgtdI0&XE=HXSCp1S27N7u`|?miz@p}zEx2Yj z%uE-lo)S{1lf;sK%A|A=G|$%gF7xYLvb`Rgkp%)rX=sPQQKC1O!z}AdID(ZXQi~01 z^nu)Sx7!j z4p|_N1gpq7QW8iClq5@$%}~hEwPc;rbYk7pGSgEUxFFXt+7`~pq(CK^0DuNrDS#$o z&YZoc6B+Ez6`svA;Q+D|NjEk_vIUlm^wdmbf{%m=0*Y1A6MVd8Mbzk)OZqayR}<)^ zGY?t1X%Y+Ja@3R^FeU)u<7$?e*sr3|T4V024(N>|dT2B|bS zTeF32*7GKL5nzUNT(thgV_y5i+E@52?E?BAB^u8Z1#12shi(#x1V7BOXKO&1OC;U z*O&hGqxg*lmu>guyyZK4f5r1!-)q~e>-u7g@;xli7vG%TcU-UK^iPkQ|J(K9*Kz+p z8bAD!AO22xv$NB4|8pa@FKWE=Klpd2CmTF`d&GVI=9m8KyAkgVzjT9(x7fZ%zK7N_ zddP;pm))!5^=I$Cx#H>D;|E`G{(bqaOXVl{SW-894=Fcpt*Rm#_OGWJY7c2ud-L9rP)752L)<*~W%vVxL?;eEYNV z_M4x5e*I}Zag)(hTdF8wO2X&@W3soLS9Xx1d*mh9T+Xnxq~^}-vP^TX578u3GZ|pO zqqR(=RM;poh!x7f7!m`pMeONaxTpd`$W!5S@({cM3$?H$LCQkRG=msTj8Z^~qFe&c z!~g`0NzJlROvb7#Kj-iXG^lDw%}gR;0E0k$zoW}GS;-01F7p~oUouzKRHIT=8$~P2 z;32(^#76QAKE$AhqaKctUFc)LKcM)LRISEeQJ+KK39ke zi{>cP7O~AW!qyzJWz0EJ_Sp6X=dR5&n=srF2vNi$vY5U>e#QG~9IonSFLK3lMBI`$ z8gB-#>KDYA93y&ir7q-IB*u#Asj{g&oa^|Y)$%;#DP1Hp)f-nt)v(RH?jLqs=fXac z6jaGkRz(UPvp)Jr7Zev+$9%Ypqaa*mFow(Oo4s6)B2q&Qg4AfuXXznMuFR&SP^A=V zG2=K`87cXA{|K$UDoc?`W2cj1pL+7V>kt*K(Ym#!UyFWfWny`v4%5(BeLgoHTRTUW zaTqRywhtVrt{g0*hWO0BrUNTc0%PI^`G$ER@vGw~Ipf5QA_1M!FLQLwtYb-J_yQaxWRRg) zWO|TRS$R#oN?v5Va4s{>OMUF$2N;y}L?=m#Y_eR$rTaWTYBs5RwI}t$v#+5G@(eVx zLDHG2LdxTXLaffEGmuW75epJXeY}bYS9TJKOeBL0Q!@i7NzDolh(tIt0~waBu(o5L3{&8FD}63H>J0%h;zp-rV|+ciRu2^k2Nl z@#Clac?ci7?IJXwoM*?ZVYlJ# z6V{LL>;g069&wuK*K%`x^Y!V~ah=}YUX~`GJkd(LdJ}rUk}MN1p2%>)i!GW-OFd&v zJ-vDLaQ>6KPhWoeJRPDwf#BAI;T*bI%iwl*)hP~FVM zx<-?ShRRe8tZ6IqVb1GQDr2BG7f)wTU84{t#V{zdnYAhtHZO#i1;qp`opA&f<~Zc# z23xG=GK5VoPIF09j>c&$)8so2d$leyr)$g%xzF;B-bsNiu{mR&4{wg|<}=qAOjT43 zBFV?}9pCl%x*-rnq_6D98ytUT=d!4sYYvK0cje++7mdm<+$>np&1S?m~S<_opOtH&y z6)f_&{$$L>}U<#Q=VyY;wZQy-T|4kjHFNuR?smwxekg%0ZsAEcX*X~>Eg61g^CT90hN5%GX5 z)KMg*G#lP&UYAoDH(ML8L_d&?#?r$>r0D>NKnJTEK5czj(nB>BLRMX+G7HztCDM|X z(h@5wV`X^D-q07%C7hCGkPHcpY(28AzIXtnSZavMsMuJUfy3j@U{sb=LLPbUh(fq> zF(WVtI0+{h65;V^Z$-F}(v#j=#+pK?VD7m0x@HCy5+DjusS|XM0g#~WVV8-U7T@&N zAuGju6u;~Hi|hX0Q#t+O1;<`DY1;1F`sD-0dwU+aO6K6_vEP)?Z7(IcVq?JNps(xZ zPw@8JeEXMlxWf3M&F^jx|Mv4uMf>o@awAWFa(Xsx%KH}|FW4Sgca$d_r`e~&VYB{Z z?DOoyz5HnC^Zxk3Q*3@=H-2X2AI`_`WBE_YN3YJ$e*aKj*q{AkegC)c8-Kri|JgWx zV&5;AF6@cd-Ge^=1pJGbFYpSyAe{XCdb#=h;p#-m-OuhyeBGWu0e`t%zTx)YahJ?Z z$`ja!xY%Nuc+TZvJ-yNE*Td_#fAaO$|LoDkw{I5xn@97RTy zl{u3h+2h@7$D8@;+1H1UKOa7O^VRR2zM9>JURJS_)w9Vu4p}n!xDeBoxrhc!+K^RP z)EEl1^vEWlX7=9Zb#WJhwNKK&9i zY&6ai+1lKe^1PqEn^J^L&>?ejS?8SX68WA~i*(3TWHX4f_sAh*i247MGmGdj{Zj4E@{qsmBb z*vHK6GQ@fI9u9OdV;u^lKqUu}4K>NGMT_c?kxbvwAEcc%#aIU92HC+SpV)HLK6{d^ z)E-QAlr&Wz$vrS~>NS^%bLuHpp$yvmR&vP7S35isQu&O<#lnp+RL`LQYZBOgS73&%OA&Aq_&gWCw0pZD(zAoL5l>|#J zz)6?SRX=6cTzAnmU2k6J&tS`HWF*;-YD)LkGo#}hJ z+m^l`PvO#Ah;!mi_{%O^)aTqzgKRgO zh_!h;3l5ZYW}7GP7kzKbT(e6=rtC8Ilu$NeqU<>2RG4;sRSu!i!;epk{&09w_Mh`~#qe*-d{nERJi>G>O zMirR#RB-_v-ZA?Y17Zs_37EAU3|2e^XVd| zaa}H!p&8EOV~#IspKAl$(R{&jpp3)wtBbn9v-j3zufKP+57Pe9#rV~`&wu~jAC&Ej z4>!Mxdiwg})yK~+E;ditxl4(OQsHuNz5D#N|5rbWeUZQYLB0F>@GpP=-IeWs_~D1> zv)eUFnZMcecD(%X;pNNv5B|yU-CqB~Z*J{3?B*lvE_Hh~^iV92373jL`w9N}?~@~c zGnD_QEc(DkU)rwf9%Pf;8lP4Xh=Wj}c_NTeB#PuPVY7`OJaeit z4%}qfrytvVy;vAo;*7XQU*Q1>NAGQ3&VA<0el$rwBBIJ9NMTr4byL|-w%wKq+sl-a zp&1%!iLJQM55iT|$~aAzyXn0;I-v~zIaY)r5{Xr*yK2(_03JMe+;*XG&~7S28FbuY z$`15Q*htGg;t7Z%S-T=8;zFV(SNcLdpx+SzYA*M2dK2e$xz%%T`EXG0QcO%zCC`0+ zvmV7nR6PL|$)T*QbRLaMj@%+x^wCHo{rMQ@2aA&NENcQvxv9fFy% zFs>P2f=d+hQ=H!Uy)acR0VFc0&@P$GB%K22ul?#@?)7dyyJWX5{2-qAv|q4w6(m+XX-=$Q^+O&ps_ltMsKd(YOVDfJ@j;(2z| z@DfY($hKO#u{VpgWMoD-I&;;_-d<$aBmwQNy%%w{=4m|&yr+9>ncW{#t-G#QUQWKg z^F?6=63Iv%y>BBH^mEG4d~~jn^W#8JqLH(3mU32>qN5@s8Ec2Gv@X#sK+Uuc^yCBL z0ooXgsMo9;rb*|tm3nD5mNv$ZF3NsdKUw^^j&T8dVViTb!{P`)h8k;)fIE9T1aJC$ zNqk_>-^27>yE>%)S@#Qk^DQ0Uxm*u#HZR6$^XD({!TXzw%@sI}i6XeI_y5-3eynia ze)*G!H^2Al%?BlZ_2S#VpWA;VZ$GT3-@x|_bvL~GCjR($9^U%^@mII{PsUIF3;X_W zQvS#M_@k^9Pp*I`d0v*+hf`G?+&=$<`Tn0De)b*ozg#}}r+E5*ukWs=o9~oQPS_o7 ztV0&vEeJ3z4UrSeBo|oUVSe-F_4Q9*-+g)g@@F@H^7ig@zW>aZZ0BJ%1CaHhG}TSMfw#l+uPgr#p~(rsO?RM8U%r!R7+DZ$?u z`Hb=*2M;VsB8fnRWENyaY?1pAU!!%5nI*9gTtVKMy&R2|MdIG$U5liu6<2Q-U2B$d zoa}xO6&#_Lb472Ac!b&u zVN8RJ z^Eww5jN)$)Um+B>5j_-Kms^wF9;2iyWCa&XtyBv^t;m)*dgKgU(;y}qYEkS#@J<{d zV-)dVU`kwqLUs`$%i{}@4T~nr)ODYL2~4Jloy!=x550nKvs;HmMoP=_R`6PMU*yS9 z1YL4n$(8-BjbE1{sznCv>(Kgr@7GOh<{teb`V$9MxaR^psj?Qt;IDAQK2EyYNqAT z+1GNG&8XwXrMPT5LbC0GWB8rbH`NB@MJxtUV2+#v51Dt#0vkknfh!7hK-nO7gk^4E zTTl?^o)60g^HRJglgR3<$uM?PRWUJ&sAbn|D(^&JnOWgf@J9HOaiQ{_2-Q$VDOv{| zB?_}>85OG0nG4(l+GU7ROjzt4*01wnfL=g_p^S@+=P3;Klo~qt{?y_jbd&iMxm8gL zvc~IB^^?cyZflH-%iyt*zGR;x z84#$8MN^~zSra2RA$#wKzP@c_R)ODn-e*Xp1&=KrR(V7oWC^L_!4|+8IJcJmAhN&+ zM2l#Ql6@#GN_TJG=BBM{dUmwVl3qOG?BUK0Y{Nt%!sqCB84t0p{@5&vhvXrWqE*$z zs?@FRud0kX2z?2hLV_aVo069m1;$88k?es6VyoP%Uu@*bUX_T{Vp|Si6q)UAefaX` zC{ua%%wXGPobT52=}4hyT}z%UBm11I96Ik;rXA;DoBI7a+@JehKi__;fAZU9Iu9Rz z@WdWAUw?t;b^5g@&+cV<5KNc&;RpDquiySJ{_R)4VcWm;?7g47{^lRO{RkAL&x@?ZYx!~gtuUj6y^x8MHudy|`f2>T5=KW7A7QffXe`Re-o z|9pKYTl?XY@$Rd`pMG(3IhJ{uF3$DWzrDGAR{q5)ch$DfN4=24l%}i$hbd*u72;<( z&2wLlpWeK_JACu?XE)b>crV9$%Y*RzpvU&EFE5eNwXE#WE^*ZL&a7^)_7}s_ru$RB z=HaDWzx?9v-H+#EYj-C1C}-q3F(X|f1Q`^M8c~x;2@)v;Aw5-t0a}3#Ot`V5K7Q@M zPN||3V+USiLSmBY{OwBGa3ri>SaXb3_Va4GD+=hRg_0 zJ|;X6ojts#1c#)h6o{yt#~M5CrR>AmoU05bR^2UIv0A2WH5~@4+)zdcXilFs)`FNN zj~&^2il&*I)|fqeAQH%6xPU5B$qJq|jwL*zHRyxujRz=2_3^}B5krV%F(zpyxzRMS zluW`?)4liXacF)$_g*3k)-^Ly1w}bJZNaB0CWQqR zHn^3nU>;2Gio;WTKV;rVW)guO2uQ++Ov$2rZzxZuF4;58IRN8mnG44knl5cDKn6HisuOB1si1X&I=9F(oj1oY_y2 z%L6lI`xM(!(&^VNfpvJoAX)kZD*GDN0&GDX0QTzckWzD^q*H(Ho7rh=&@F$7B#AGSHhHe_@O zu){q?GBQDAmCBN{;tUGa7$lN@f}ax|x@P*)aXP23E_O^kr1X>!xi{WwsFqR9b!48L}M)#IGelMVq>m?{SS)nAx^%$SHO97G-yC}vu#I@*fU{kXXqr%q3J zK@4bJ`hr*!4vf+_7E!Fr*u8mr7Sh6cE9)vPxgY~f?z=w8c$@eV5Qc*H-5(kZSwtR$ zcV=3oS`t>mnqx^of~7r}zM&EvvnBwMuF_EY3SVS~A59^mTDxi&jYI=IU5JguMm)q^ z;|_eCvXWqPWTPLzBeOy`uo@h5xI%jlm9n(ak1Za~BJ5RhiFlSlrw1g1=l})Sr(Z@q zNqrGRtXLZuJyIo`O8atYe|-Dtvtjd#U|&3buQ#w<@#PU9_sc zubU2`zW!}){x30ofQ$b?pMRkje;~UnIev==x$W}J{rmXhdt>>7;}8GQ>7!@*N8jtu z-r)V$SL+?(v$s!v1)Kl={#!2(m%sJSFUl9++kW?V@aO+={gv-*>o3<|_z>e?=J0~j zSR}o~sqn4I_g^>py8p{3^3(U?uYQTApA9}|eRZC`jrBh+`xACQ#`EVWzkq216?Nd{ zmQO~U2EIKX=9_C@-~G7V{Qlwf+vV-MKfLSz;?8nzW8Z0SrJY|Ue#$81PNf%y>7CfM z_+It74EM+R`Y^nF{pC+TyMOoYaBRoBz`G`M!kII2VTK`11YwCv4rEYVj8u^lQe7sf zQaK>T?2JURiVmWkTvf&ayA3XtS13!>!!VFg*mJ}O^qUs1)-D8zOff2$0z}4=F2OMd zi>yIqbhdyhCSz0y7im%*>`CdA)irITnTV(gnPjxInLF=00!e~aC>ATrEV2!~@@mL| zGDMbuC)Ugr=*e^swqCGEnnac6;f$U^1jD6w6*aT+7@a7Hfw`v4up5at+DjQWWzwp3 z-~bzv>RDPEVxSl;qykJJ9qj2jyUcUq5NpXOu>@366k@2J>FFKOqdr~Mj~5>Ym7N)M zieXC`X-~DjuNNg-q$~1KCG;5oR@5E(MV1Bq2!BYt14UYqW9mp5b{bDhSakF}c)xKc zArdpo%;9OohYn#vVswms1bP?T!QVyK9yQv%$6Xh|$;Zx_kx-%_H;IL9Rd+* zeKN_nwk*+m6ZJ(>LyS78dI|#3;mzS)C-OpvWzdI76;d2S^quFzwbJ&Z`&7V5TgU?%usy0SYlAPrzHs7t#|s zfrFNJsx+EsglHwT+i+BzJFjbt$2A(rcDFs=Xi3r5G<-B6eTnJ__Se(Nu<3Z#>ixa_e zt8ruJQR{`)HjL}EYm|=IrasGh6@BM*^w@{&GYizfn#10gC*FJG*{us&84}D~6Cx=L zNbp(4he7qwkWF}r24sLeWl1t4L}!sBAtF^V z)v_Pfq&vo8++9q*1;*41_?|B4`55^WSL-Z=A{t$?sR&Ke;M_h;0U2+L{+4dkZy`s#_bXQbMHzX@=aDyqHueOc@gv5|3UQriXG-ox9mQR4b}l zD5Cb5C`DBC4RcVal+Y8bx<>TEpl{QfKlmW_QMrN$U8EofaJ#Ucbu!oqmPZcFa zLe`KDXrh2?@`Tjr5=3Zv9GcDETP`yqMAEX~hP*5o<9Y;!=T9!gv<|~cSp)3cE;<(M zc2cf&c&W+gwvJjOrj6+juZnBjJX^p0Yx=%;mty5Q&#B<+uM-r=kDN@Au#TH2zoru>I9#_#b@d;^Cw7SMT2p#qu!qSHJ=C zKDHlVZ8)B=AwN+r!Z0~3*IzF$UmkDXzPq{o=IzT@@A7aL_3r$%pSM`pqPJ|M(MDwy z4I|B52Z{|Mf%$ZJ-3~AHvp<^txDBnBk=n>Jltv9ORH+s$UQ)3I`KTRIp^Ri! zt%JoS@>xnII;@Hf!c}!vl#SXp1%in4pzD|gF=R}v#(Y?PS;|IqqLKo6w0AQzBNNfm zAJrs}2TsWZ5GpjyCTJ!z>=e{h>RBP>(V3*iN*##h%=5v8!NM7`h?pcjw!M-(^u^|G72H2h>XNm(GqjSaXq~`XGf`~i{`9s+iBUy z(q^0=B##s=Xi&|`J!IrsItFJfggj!=jp zYLE`k7TGc-V2#B^M#rWHvlGSEVr$tJ-3&E&v0^>++lO8@{JD27DLWaTi(FwHVO!hn z%6PQRZHZ0|JU*qkL)G%V$`_R&TLu__U#EY~o+4G4reYjy*lJotwifN7FW6j6S1+Vx zIrMqXzI2qtNJvV_L^OAGLE2-PAT(MHooH3!aT>I)u`F?>9wZEyq(U|#6xC`zG6oV> zsCJ>Y(aOzMb{QCYKr=OF`U$;zNT#3Rw?M)zd&KIF+1Zg%8KYE8Lpqa)JVxGmW-ub( zMcx1stp*Y#Ez>g4kqhAwOiUHKiAh~ahsNX3Kj744jy;FYA~LE_v?`ym?Ek%iJJTfF zMV4pK6L3S3nLIHipbj(8pg!+WC8Ok);p5+3V^oQS`GfT>ZddR%btch)6pY1B$ zyL6We#EXcbtNG>L&-+*t;w*d|U_44$8C60asT_pEZoRzD3Yv->Ebk=@QIp6Rkz306 zXnG*`5?i8x1GO?msF{+~&brz~@(GNth+M54_REpl1ow0Qa>jNdnUz7GG<^}8pZb%@`0XDW@;fV5pS-9@5dy63Bzb)7$mb&GDf^yufHiR?JWV8^M7S`j6rM2R)dCv7iZ z@$>WauwKSeim4XKfCLSxRKi$| zS_x5A+917Hs*0$I*3F<76X(naw>RYHI@hwf(s9l>#k{OMpSF3tFYO*7NLi9|rcz84 z2*4j8w+s;|;uNv8daF|MY*u=!4GCl|Ldzv(?Z6C&A5cI|!J z@S@=9fVM8jSy7Uce+~UCPnEW(k!j%8>~dg6tnMD@xn8Nhz{TInY=l)(1x6aVWXw1pRPMKEqE`b0vlPOBOjf+ z;95D4KvH||a=q;1+Tt9V5t)%9StKQeIuKPcDmP+PxYgXRIh<|vWsV~KY<+ni`T;I( zDqd|Mps->TsiI}HT06l!KY>1hO_0H-3vXBHlm6h77pZ$-L*1vomwAyfWLQRo%%N){ zB74u(J@SlLJkS~W$k`H89gF2gaxWxmkUg^{Jt`?rb$D(~!~rRp8F2@BCpv1mGCHM6 z2ovc}Btk_^dr1^vZgM|+K!Z4ktr-*%4VbWGP=Zk<2E#a&%_abiNTg+Chc@P}*wvUM zBH5}ggL~xaelLEIpsLxbk0I9#WH_ahJ+&oH;3>p_sgM$x+RnBf+ezBVGN>t7LV8eP zq&$Aub7YHH7OZDw8WF4MdGdO_Jo$;}uD^RWxn54AEXH*hF+9=9MbHEbbVP0;#&}xt zeUpi6$eqY;r)SEc!TMVL{LL%;i_dhroc_l5pWffz{{H8mepD{@{MNKgf9b_|`$75L z&Zked|Iwd&?_b`&`=9=+AOFRC@~ao$H`aOdCtGe)mJ>Z08_I-vp1(Ms&c((uo?hO6 z`tnV=7+J5Lb^9ydx9dy!=PyB&%Zp7x`LF_muY+R}q%KFS4OXI6zrD})2CqNAU4C|Z z|IIh|Z@)aB&yxMrqb=yK(!Ypx&D9VoLb7dY+to{!7tmwqwV&pboFBg6=YKlE-vm?4hAo@Y#Trw%0JOd3hr!{Xq zXMg-BBL445Hqd!nm#b8WxUayH5K0kOQ!Ug-xBX;w7YpJPqEf1`Dh5-h21!tv13`^u zF^kY>Mixbl1#|%h?G=agLk1HPPG%9*s+@dg_hd|j@ z!hz;3@ZcfjL_mxxk`RcaBt$tuTjZL}rKhHJqLE}89w|uC>(IuEai7yMVk}LHN9h>4 zTN4$UiKIxh=I6PkAuDsnIIFawtW$P~yBFagR7&Ia(IwaUu3f)e zxHt`vgn=l6F-b_KiqLQ`@`{|*pn-GXc#uRS9iSiEIhEGeo92xx^U zqI>04L4ZALGg)e5Mh$%~%L{dgBo)X7?H&CMhbP7li?r-ZI?)p?>0~oRftQp@xu;*3 zmdb>z=)qN(ir^AFNjicucuwIP_8odO?+xh@Asx}dtMq5VXzq@s$9cul@|1A^&gd3> z@Uyrw6_FC?nJd^lmYye;S;|P=(SmU5%2?D-HIEjlglR`2$;fo;WK(URGuG6VN)1Y! z(ND-H@Q>^LyR!Y!{pB>ui{}?h)i#CnEK67cZ?qUAf4)&td zhTRbsIHbmBE&ufJ)9-WrYxNgC$9Mj4+_E-b@f__h)UjXdXJ4Fuop1h=?MMF#f93x= z{p!Wj<&)`K1MHGxGVd5zeLD9IPNRP7?8_;BROGhi4_;#X)$nzN`{|SKL;gE8zH0p+ z&GVl4*iIGG9?u7~k#nXWdNxcD)YDykc=P)87himS`?ELKfA(;Adv~}yz0H2E>%4dz zBfd!g3C*9yxPdXVqg-3M+=kT}*WK zW=r-KkkvK%0+|JceG<2139}hGvq)CWQ}7N_grjmOWh@q8raVG8Xwj=Ls%NSphg78F zG04@j(-zSOkNa4~VLb{kBFS`Sca@+fne9kBXe}y(Of{Dgnc{A62?qqJk3VyQ3g2Me zc5D{xi3>_Q$LYLBtl)a+xLZlM3I$n!CFPjfh=hP7%@Q?qlDyDl^S*>r8ZNLl_2nVs z4SW*0(x6qAK?2exkz^tlM`Q$hC@4}Jbrm($ss+BHo!iR!Jm&c(Pw&bZTfZ#PD`6yP zR%xRU&TI?INwR1Tj5%ZuTl>0p@pg{DK;6=+ma5yLUbXl1H6C-qDel^QDrGlLEBg!) zqZSe>QP#ek=C+)>w=zO6Wc;w=gNY5xIZp#PFiwU$rKS$ao4&kl0|%46C|JTE6w#8+ zqMb92=ryC}>6Q*Z;mVMdYA`c*4mqEkAvMStjV-TG7bhpfx$r@m^G5~7r3b|=Gz#0HNQV~)&;`kMJU^9Ay|0X95x z9|P#Ywb!%ep-N_0>OgKqH&%x#rIw*IICE73B2=Q1(AsR&Sc@+ah)78=RR9r{L3Pkg z;HnMeag{~`$6O@l?04R8dM_a()@YGUZ5pPFBAPu}85{Php~kb>!3O$Tx`zjr%q2ba z>(rkhje141M+a^aE)uCAEpv$1$j_314+(?=rxX!7J+m{C3%vKWthQTX9}p3!J}G>! z*6`*XlIhvdSFA2bN_72|?|z?YmMmS6k$%WH1p-ERMlja3_jAiYlF`<_&H^o3m=G{Q zO@Wlc(a)K4#9a6D*pG3(^JeBl$uqSv1*+-B+lh5sHXQnrJtCBD%#=(D5J(MLh&M|N zGG-|r!Zfl112Rr)yH`-fQiI|S^h`hW_TY)14|yPrF+;tt-aR50*E5IYs03<)^-*3H zT7e!|gAOK8I>%N-Mlrc$xs>kArgSwgspccODLIwLNoR!j)>74K5pbZyM7b1rW zn{RFE52oEW^ZD)WSihy4{curW19TF-P?vs+WxLT2_T}~b@b3ENv#&q@(W@Wdo*vHL z@8jfs?XuotmNl$r1WFa#X_jK5V!Bb=S3S>nH{ZPb`u^n~$!CZ0=H=#o<*6^O2P>z- z6r+m65~4|q46+l#pm0f9!LiuBx&TNH@PTTy!m?x8iLyJ@G)f&Q8+i?=gmECLc2J$oZp-0K@l1MPk#g2iYRd8gG>s+091$_X zF+#o0T+SjI$>ecx8U$oO1~{NSil@#woheOYD?*XL?%75Tx6H{qvLy{N(~Mdq1xaY^ z(XXJ8+CQ!j@2;6lQ)C~y`$2LxU0^4Aqz#N920^MYk;p(d*~{4WF;)P>;B*BrW8Bg1 zwH$QnW!h@?k)y#WkUp^P2>$a~R?V(jpH!eq>#g;%1J|uQZ+U&}7p6CecX9|o7nS2w z?q;d!8b+B#r81e)Lq{|nPo=M$Umj3zc=%Ews$o4uLiZls-Q#5g(8t~kv*PGGav-v3 zR)#Z%w{5iN?NfjHIjzWaR@$O*%xesHQV0=<7SWOh_tt+xJC*YdU*!^h9GN1iZV?*p zaHJm7=49I=S39%JLQsV^vz*jx4vV6!A`GA8_?1c*1kPy!YGh*anv{${(xCx?Z317-+yM;3Q>6MpVzc95*RP)RVnLL*j9acTJhB7^7Wc<@>;Il|ywgO4XS9ljl0J zLG~Pbdt5i66ti^wt3UbA0Kp4*1AFE*u_B3~8RV|At<)z87EzO(x+!!*nFJ%aJ6FKeyzK^HPeecu5b^W+)H8^K9&j>0|IWH+UE|9pA?x6`Ij0j4=5R$GhginGswRmb$ zMT+VqoP-^^sY+#03OyxLBV;_GJb?zClL8t<^AEXnWN2!EPLhjcf`Q4>nOTLCv4w1d zDWGwt9z~_3nB8i>G4I4iaimjc;tW~H35v)F>Bvo5Rb5!Y2FcszU#={4Di`8NDPoYt zy(7Q7Kk7MdU#b4`{OZZqcl>GCE7zMZ;eX7-9^)eZDDq=`*xWpj%H(uhfBy3N_S@fs?OLu6;~HZ>VjtO} z`|2aA$W6H~%AquwtIT!x*)&QKymM@-s_QLKnf$?AO5$JbkU&pq>uc=d-tv7Cc<_5#XkRjaB@B8)58uN#?}0HAHD%f$7f}{aWAw-#*;n66 z4kojO@3H^v`hCX@3D$xy%<<$ zpk_g)k&}U90;!me*>BSgMPwF9iZ;s1Wl*{?H73-rV*Fw_5uKuNWl~A&-zNT}YaQ{b z^(A=r8(yDdze>Pdm&he_ckY--4XP;*<>h@&>A{W1lviBt;5W!JV(D0rj%Y~83jmeI zoE4KP1Tox3)Wif$i6O9&)2@TQ>yBA?(5|`>0z0DCgr_=43PfP|41GsQrGh$OLuTMW zbdD@v!jg0Vg(qYn9>DLG#q(yGYmvded-x(TN(5@yI9Vbq&@-DiE z+pEMcs3CZgQ-^ec>ZtCqMl=+%h=5b_Fm3DkdDZ>JOci^$sZ2Ue$P?onoI*%YgbG$} zif7V@Iavo!k!Q$(asqF#IAf>3SNnd_s(GXY4V2$1{#Ln&d?DiSmVRx~JNLwvb_Fid zt;nR}`d7dDBd~>x;GVJ~p224h#hB8I_@a}`!BwmjEu7R&##+e|*uiT)OMXHCM1%OR7-`6b-|O$ zQ`Ihf`SPu*Ll7*9xrj`#lIhTa)TCyNWCI2)fFaq1TSkK$asr0oT6pnt?MxLGE?SP3 zSM$&6V#y*`MuUJp!~daoG75N)En^ykC{+5GLp zXX1QYzQAW5uI2t9ukN^=7$*B?#p85Dyegfi;3)?1v_|`Uz5aCBe1HE{JDisZucm|E z{bl4Ee*aeLQvN6Zv%^3A>fOKU|9Bd&|GNKW-S7|f@z1}oua~*)>`LBozQ@m>pZ@jl zJ{_m&KYeq1dHDGE@4mZVmXp0&G=KF?JQw-5@8xzYe=;8~!+!HAPOE(BH6G&ar~PK; z-K}oUd1rsh{ff8Sr{6#R`0?+5e*BN`AAkJ#_T6JXT>Hawwzf;Gz2(;THr|geD1(zO zI^UU@6xaUnw7+}*c6PzyG8?9+ZkyMC4Xh;#@OuLss@l?!3r%HsvV``xr zo*~a7nF6rG1`$=p99yy@Tm*nZ6xm0NE+eBc5uw2d@f=O|t40?>Ni++cQc9o{ohM|< zh}_VhnN1~&q83v*h}{ZGD2D6qarJBF5RejrEwu*(MU(?&00VS@GKr2y?my@F3|%Eh zUq>^sI@Kr>%L32Xyg;5oXQK#2ak|X z8G@*yOP$VBAq=T=o$E4H)+su%8SI?p6t3X{9_G9A2~Y43K~<}&X%i|lEfWIE*xI`L z)tXu_8E^+35*#{)_9E0p^nGp?>YMB+fRNy5LFB5 z)ClRAcYQ5+G9)r`&pvW=PYn#ww6buidXl4_?$sv@>v1>oHknQztf@2eBjP*cNCNR7 zc(8=HzKr4ENMv#oZbp|(L69OiX}zuE2~R)yU{7h$=u`ncSxZ@_is2V_ebwx=heo6C zZGY|_J=h061ilNbRMjj}DygPYLJ}Im-En1MlmdVb-+Hu81w;^%REFqKKp>{GlqHcK zG2-P_ITS7#yZf4-gzt--kM_-t3Tr@T*eUvyw2UI9iX5!o6dl4Ya)cZ`=OL5xKsj*# zz)#;X<{CG1Zm@(Mv{VU+lp%lutM*k#49^kVQnthq@(M0QicZif2rZB@rHHpSVq_OO zBv*-DaAdvLF?p9QA6vY4foL&JOhnl8W)EP52qH4lpyHN6k~D`6iqh;4cfbOviF4}7 zgQ*D!0fG=B8`Tw7?YzVdy@r4VBB418zoyyfljO(fE_*oO$zN0A>nlj0#<(R!K1S zu6rx9>akEfWrT?pCCC&S(|}HCVnhlDM3P28e{PYb_xP zYC#@TO1P#EWQVAaGRB^{rH3O&>Etf8A)Pr9i}K2R$hZbK8Y`^E5pomqKz~o?RC!oh zrhAc)lJ%%KnMCSvD~*TUO~MLZ3%;Z#*pnIBg?$n)GAIqYV{ADtz*Xd-@$KXH<#=~E z-mm2@ZeHC<`+}bhV?;c1e%51=z7#oD9P7T7@>!mLD&seceKr5df4Nk|_IUl~*O>ly zrM~_6`TzOd+y6Np|IdflJ?6iw-;et*`r&_7@#c;Fa+$oFYTM7N-{E?%U;J*D|Gxk3 zP4z#sKYNEa-!IRm*WAAN2B&{HE$^=JH=m|CFW;2q6YO_fTtwvjF{Teb-`ew+<#~_s zJkHngVe_|7AD=$FfBPREpa1qT-#xaEPvfDt&pyex9>!(c{JPDfnjgW#L1(>7{bCe< z`+5B*ynFlOx1XN%`Fhv3%)aM{j075UXudp2A~RnW9pWDTB^ZJsF%we~9vLZ_$$VCM zmWp&nIA4M;(FA?CduF&el%$oR=A|^lYN;ZX;*vx1s`;o+%oRtYOhQu;!!*lrmeF(D z^-1$n&4g;I3MMJHh&Lp13`DqZ;X_3c#>W?XwD2G1gS5knz9tTw`nrE$!^h10?fp=k9A#=5S0pF=rgTi8QB`l^*G8| zCn=L^?`4k(Je!=VmcoF0c&^X5>=7fFMM||ht+a{vF}`#ZrZDd)HxwtkVj>pGcbR{m z4uk;@%m-44LNZ-EGlr+Fi*9F2fdGOm$y>vHoqkIH$N2bSd5~$DrwT1;IuVDwVX$VL zm{pP{Q0=)l0AWC$zqDSpFY=wm-!nyVtEk91NsO1EM0s)BirxMKXcJmHtGWM!acqO@4%9xD145SdeKVcapC)A~6C+Dau;a zsxWMX@9c!#AC_0=4iE34X_7;YtY}_($JlFMb1i+J`2zQRN?AjcWh&uTyXHvoOo3Zc zMXi*>V?Dn;;&}XYI38~Q;*Y=DU$eZzU~I$nK~JBjyes&{tSoh_+VtocWqCN9|LU9N zfBj~8`}X?ZeEad0mA@{>AI5k8c>8yd!^hvgsh{hwPPcdS;i6K`c7H#ui;aryx?Vp# zzx$|f-&RWd>W{`y`=_^ZQ|JHvpWc5rZ~uCI|EoIvOTBq}{rrRdbbVbu-W=;L(X`%6 zeI-sGj5d{rQN|co?Vmn+{uH0zzaQ^@{`vjGhs&c+PuJ6PKlq13v^v%t@vyazyXWb) z+#fE>;nSqet?j>g{-^KS|M#tb{H;BA?AwOeV(gIsDC~-%nj+u~y$KtHD?&0NaS_`! zBO~$;9X>)*q==uGHIwl|?C+E$U#!#Jy^kTPSxd<>VS4Gu;g)#Cq-d%a#iY{><{PGn zg{YW{*_drza_r7)N|Cb2oV8|9G9^JIy%0&3TBpdj5nvBI`$$7#B&=O~8}XWG5g0#uBki#liGM3Pr?=V78a_#Y7;l5f70QNY(iuQxQ=swL}n!tf`7PFwZYAhM3oxasBY^vqDQPkp#pNn&^%T zL|D!;$!W5i>fHBO0V5}~Qp`f4+gQi*=luyEIexNG^OMz4=RPMr(#MstlOZ-FccmMg zx+9;15qX2LP>bLoa>G2*&0WV}ZphltQ-37}sWFhA0ZtMNE;)ze6(4TobevutXX$3E zW#*bbwwyY{gvF>r<^B=vM=8@>UmuZhhTbU;hLTw#B1Z%ZWj0M|sU|k12raB&r1Tu3 zE~=C0Nu()ObC|Fa9?^Zj=6H@(^I9!Tr7%>YFa!m>g_Yp*;O5)sas9zZk6}503!$w{ z>lDwl!8|f=WE6HG3PqShbyofg<*UdD9q@FxCq-0~=#no$Sw>`rL?DBUq={ZquNjf) z=vzMSks~lXT5LT~Qx-5%6HydmxrsEDP@9bh!Atk4sB1Mj6t&4zI;>@gN--3w5qc_e zN08}p9pkxi1Op?-h7s@RKPa4LQIVGHMui;Q-H+q%tT~X z@W2R(uGq;7a@Ak|@qY?9rHK}^xugY3k11i`7XIwswh_BOAwM(VkPH`t>ZE!!bxOjD zW)_14L4_B%B}yXFN9IHv%&qF)T2#rOB5+)gxPF*Edk=w)V>JVxF?zRdV#=HlHVEFnk$NqsKzFv%N{UxlF1nZ81tYa(SpzL)%6`n= z#W#fycvAx=p|MOn+=SoB`ic0>=k`aH|HZM~!|$ea9b4HSw0>g!h3cP|Y0L2`!oxFo zh`ZbIt1o!Dwc~gD=^yvU+W&rfJl$gXPhLI$_WtI-s`ZS+U&*gj%vtX9`3;`y_)**O z1Nsm3H*dXvdp-OS%Kvnpe`@*PJ^b*e`ux9`Z`bzbhfeMP5sx>9aewpi=KR$%-S3o+ z@(S^q3os~S?BnUN>*M3{^zNshzWv?r*LNS^|GfSFy3V?f$9dcrw)^V-J3I?#NwINTrU{mkyhVhWMbUrPUUc2J8mc z449_^R05D-Jt^O4N}*4hx+iuTX_D!gZD{Salv-yipb#pAioAkops~nn1lz@rKnQ|VJx%OsoKKnaCxlHxR>dR=~a#E?EM(Mj!{Lcog@dMV*njJ zIVc|}Ka0FF{-Q=ixQ8%=m{ks@1T!^XY$P}&CY5QDrH}-x$I>%Bk!}eRXY!7flp=Dc zd4jf`n~Rt&ViRA6KopHtWG$j9D$&wbR6(>zVK}|}EG1H=x zBC_06+|L@j_bp*XteB-5OjvYm$xHIbDStPOQ@Q)X<}Pfs+(vADkLbaXu_r@QO74k^ zYh+BR2WpXW5WSV@$gyCE5>r4WdFaZXb8Rtu&wTM2NvGw&9OrY$!RxW;vDDS@G+C~f zc-*`9Ko*R`s+D%weuVut%4_Ja!q~;!4$jiFBu(H+51DH@9_}(nIFjm^HN8f5Z!KJ; zP|FFt3tQX%v<^CmDMk#$U_|McWqzKjm=v>6@v0@`=%`>%*?kNjKV^QEtrdC)Wf#Wn?zY8L!B%BJVSY#~$H5MiV8>M5s!GB8`8@NxET(siw)w+z3|; z)1BHFL!}pX$jDrrH-njxOtJ9#!i=-Ya930iJ(?w|ho(rLi$$HX^}X#cDqEBY^$-uX z89ftthCaw2zyk2d5#O8ruBL&M94{W$yx+d6V){9 zh8^$-l+5X+L>Y!Gz({GCo{mgd`RNs0_+M`H|0MG7HFMDNjG2-HCWA=EKxE}TT5@yOU=RitQkiH$XEiV~P?%>KdS z^PnVZq-E4d%~S>ky^YA&UDi=O11)k#x~IEFn;|LU#i>x_oLrJUxnYdVHFMJ*CebAJ z3}H8&X}3rd%@XbNWBj(fJ{}JDPd>fD%^GFY`Tm*ZF>qI=^WKRSz5UaE^L75wn=*a5Nd64}E{E|_*_&t> z{BavUuaA$9KRx~a?eG8l$A7%;+w9tNH?+Q`KkyP9h5&}L;;`7w?U#1@Rj!ZQ)BES2 zJq*p%e)#{z5DD7v{6o#g-SwjieSRfd%=81b#;}0y;(E zb_gU>_H zvAh-}q`v?NI(Ertg(_>AQ}0s+8|qc(B33!5Lu3;^TT-MlUByedB234*_A?Sy9JXt@ za3de9TA7zg2kEI)lOig0@~6x5Z{I&3W14Ziu72%f_}E7enrh7v*#h1*TfXdKT#TaU zI@{bgAFcZz7N^!x&{90ZtHoSqo^Mc8jFKDVb2>Z9jx}W?F33lDnGv%|2}41eXSk86 zT0{mUT%~B4UT*Fu8mj`Nn#3YF2>R;V#a+})?uet9O^4~kA!7?XVtinBHCC(^Pc%=$ zx2=uVJ(v_OYNygmba*Bi)HQ9(>=^?HFWvhmiBWj3aziuLm-eWrXV4v28BdBa)pi)` z=3@nIu>v)y>7u6_LqoqZb<$IP^9%jrwIU&ro@IuC1J_NaCpB&e}<*Z&_K|A z|FEtfo=K!hD%3Plh^3+)%s87BwWZp54)e~=3R?cb#a)J33Fx=3vfbdgKJJEqsaIG{Dhc7Z&D}4Nu>C|2!V>s9oX`vuoVe- zf&odC%sF!pSyP!%X3|{bK)K7wBL}#^C!~ss&Lh1?Fw@dAxF?=dE~$e$$X)7C%c5E( zAtQBYI;o(Lga>=33Q7dsRUk%5Bm>!F9oR#dwImeSQm-g}~xLosb;9=M2`gnQx`EoJFVWP^pvY5;>PG5e$e)TinzyIO;_s@7(v$f|^9!xqU zpc^^##c4uKkznmy8bV^&9xle8TkNxo$*a*LtG_`%2f4P z@jy>+AMVDeX)8am2 zP%;^qBo|xWWByi3PN0tNL`IjPL>#!Rp3lh-;7?3KCdwoAlL+aG)-*3g6@Oo8*rLpMc;s0gXy0@r*ZKd0~R+ot3BSvI$#Q=L>G!keP^ z)8V>ao}T@vH*e}UuQ|>y&Um6{+xYZ@;CF&URnDKdD;btZAC1 z5aICIET<`J7W2b-ANLnX%S;xkN~0%xxh1|)BjqAx6JE*p!ncx3v{(Kl&fK}T+#^Ak zl90j7%aBjqJcgn-7f{+28a}t8aC%4EXEX+$WV)z)2)SVbMN~eSYkUzyl?*9 zpa4of3Le=QVM8y2m(1aeL1|HkuX}skRlve=>HFhqZI1wSp31>M2iB{UXR&H>kb0~9 zs>sNE4w2}@dQf>y)gHYMf|=Hud&i=AKgliS$dDJ}MKU6ua(f>Z(Q7`9-N)K~1pYy% zWF9EDi7&I9_1Ay$>lfM_Az&svRkp zVrmkJmfA9d;*!stK9d0=;sQ2_9pecCWifAoJ>r^pOo^<5d|}O0PL&q6i~?5GnhA|` zG9|N6O;TT^>QDf?a~&@U>ewUNh!|tI0BRg(=zaRjwctdnj2cl~jxoQco@hoZvJ`q4 zhQ%L-L{uV96}L-fut#>qt`ex8el264fDj!ZG7(o|rC8KC@{aWlqljpMu~_DmDv9ST z&xoN!_5Sw5!@r;HJRQC|a`}e0J_0p!(E;TS>CEAjVVW2zkFmY$;Zv2HDUbi7o14$e z^!LcSJNfEM^C}O70(0W+kN)5O{YY+K->)9izyCCUf%3ok)8*U4`mf%7{LAV1f4KSb z!@qy}?f?6Sr5^g-n>qEHsjRjBmY7cR<=L#0h2v(wl&e2sf6vy33Lbv=>G5~J`Sjt# z!_S|wK7RI3+tc&o!+QO6xjbISGU0f>o%!Wi$_Lo*K%BV;n4rUYW7XY}S>K!e7teq$^mB#$}t zuqef#g`pYhAqimT7#Mh}<+2RjvusER!D(>nqE&QG+kC#-QKT3>Ly$`*vwtQ(3!~Op zq6?ThWhp^ZPR3(B+#HjFcmeu$2w4U9#2s#4#F3ncQp#L2JonZgqkoK8BYVW>%uku5 zzH}J^QoK$+z!%Q3dd&;UDzVwPQrIC|JjwW=aZO7?Xbbq7<<(0j85J5b0fd3OCwSCznylS7RJIy2OQkap@6z@2!hTHMJr|bqrBh z3PlpfEN_snd^?Qw*`MA<80Q;}s#47~BM}YSS(H}OGvMBH_#i_rbDqwAwqYX&ys~3md(a{MDtpq*3x)`kMltFndR83VbJVtLTNxoDA;o1L znIm6x0Zoa2mgbdG4pTXX1$BgESkhA)ajCo>oY|~7LNZ3*uKhZ;Y>nF#Pjf^YgRVE^ zL8a18$Yor0mrr4DX)Eq#Ocm2qsa=x$d`^pr%(mMJO70$CwgYt}gr~FoT;XcQ9i|fD81-M-~ zeTn=dR9-IQ(>d`FHU8l`{_WGlz1W`|zy8$g?;h7b#pQoHef@s?@&5e*F0VyS{z5@uzk2 zaP{>e@hP@P_e+e!@pL}EzFGULr_=9$e)}JP-yVMY{^O_bqL&+xK1HvZFo^VEk4Q@z z6euXcl3FFFB7S0=mPOL}JgBKwc!T7&dYWP^i?2Wuj@Jsm`t^SX~se5vU%_-mrcKACfN-FgmA{(jXfr z;lX%Q^nOlHZvkks%4B&E-WhI-e8}-b`eb?5EFx2}X|k!zIFWrz?08}AkB_Q9sm$iLR*4i~jyxfk3Awvpx=ggw zO94s57?#J<+Tu|%fYflwZB<@`R8bX0&g!P2)Jjy!E9EZ>i+i-V8U3zBUuD~U&3KMO z+t=3Vgjc3Pg^T1#i%}td5~W4%t|FKe0uUaa5hkofkEs~a zR9MA|xF%$CX;82bExD$y-q!)!x7tSn0qKdx(G*?!taT}!m?xA2rO-~~y_gC^^Cz2r zuk&oYEpiZ^|3ElZn}7<^l(nc9>Y09v?A=$_p6EdxF3mGq`pE9)2_Qupu%0W_b3nk~%|7Aw_^fon$JL$w_ji6LfOuU;p)&j5aYAx|?_3 zW31^!7-dK4^hd@A!C-NsiKIqROfSJp!3WA)!orTBv)Dn{18eYc@NTHj&x{dhEuU7o zhJ`RdkW=QI(qde_XiNoCr5yAnRTw3krBnJ~USsSoEuM~gy}rhe&`?a z{gh|nn`B+e$&y8|t6anmNBi=oijkJSNI$8(j`h&;gI@osD)`Gcv<1%m^!(|+yZ-!) zs(RYjr%Qf5PltAWHRi|Vo1b#2Klk(Pj{otEetuK_oyjrs)hk>7DxSZ&)|_s)n{~u9 zxi;1aO0ujhib@QYo_7!7;CrM#Q z9mErICl`!aq<~YJsn$}?W+xFXRZ0>mDVBneJ;v^io;nC6thm%rj7-lI0O-AcxbA)T zmdOA}E*J}?kO$E-F*78=aBUkmxoAF>k}2w$35BUlMR7Zon*&2MlczlnD@{^OQnYK| z7x*z4^p4?)J|sq=1yxjT$pey(?D-J>F~z6{sOGGYfpAJqlq40C;+3^qQ=lfYCTDO^ zc6wI~UAEKRdWJ+VBk3YNM;=-!`!x6J%3gs}o$h8!DIuFxmpN-njloK**F%%l7g43P=;Hd)5w)6i2&Ye54GW zXj!Ztl)>6%$hg*4XFZx(iiIR|)zL<{BRv@iBtZ^A(u&lgDM~G&((Hw_KxQeu#7=5p=jO~PLrh6#&m@kv}!3Cv7gNR{0 zbdTNpvqw5CF+@gTNA_&ShohAHX}X`Q;H2qCc#TlcjJPHmvL#vuUdeaIzjD8v+mWbTM7Je@go zyNrI}Ky=+bfy|TVtz%^Fz~c+s#aK;}AnKg9q>%auA%UEAo=CNJ}3oo5==Pfyl(n?*H+dC0279z8#-OygijWJEP6Q^6$15>2` z)1H6x*xq3LRsCb4?Od+odd0t2?$iD+636NACMbN)ih(fU^U180C{Z7=zx4|Bi1TI==U$H&i~xA#Af4}bsg^TXS3+x+4E*Pr{x z=lH(s!S`c&io>$9EU*jY?QWmG|Mva2|52a$MO;(nrsk%*6bC}fT=HZ#sJoUZnp#5; zVCnvc-7dX{jmEwC)!k8$Q|@hyQ4&YM-Fwg6Et(GKhB`(ff`qEN$8gi|>b$G@<{%N- zTzlm1+L~@_A+xF|0K%vfOcRo3YQ^e5k1fJ8r)`|JT~de|6ewqMrcBI9P?TlTR&WKb zxjs7D7TLW951BE-J)PMY88X2RW>c|5hNq+{%9iW1@7{;xpryj$l#n6`BQ-^AP#s07 zg^0+3aOA+q@J!!g>>2dbo(?uCn@DI~tW1T|GSA1Us?L~>@I!_MWT?1kO1kzjZOl!F zl(|j<)#Mz)RPh&dH5s(k@i=<26j6#6wS_WM4zwHAnL)njQj}DvJ{`*AaZ$Cx0WPpvM2o3v&vuR7Tgo+k=kCCO4cZ^vK%6E$V3_o@mN zOe4k9O7;Ss3eQz<^>}=dy#RCO5w>MMXAbw#%c^pfQ)zAPni zH+hWSN89DHIHd0Xk4ebplj?311H}dYnNx>iN}-ZJ?Br(cL|NCk?HP(!z3m##yK&$pjjK^8db(V zjVzfHFg#n*^JPYq-Exg+5sYE!ilpST_$LfYIl!DUr1Zc*9#ijOcPV#XwC&beJsF;! zEu$p>?4G+L(-Sfwk6D^yOMV32QD^W(*2Em4`s+XWE4Yq`oiBl90x#n@6L!SlOLRky znM>jzc~abza#tpz1dxfEc@`(J&M|-xfER{8j$J9Js}Qpgezg3RBM@QnP+S8;r;@U`kSYk-c!BB-p84cwX$yNe8$&7#Jfq<|>k#*>h~Xe%eiL-Yj4HVu!oQLYJnT zMHj`h-aiz%K%XKYVH*C_>-+1ue(xtI=L4IgruN_O|K@K#e=&}KbpOS=J$<mmEx0 zRtvFALsK25R5J;rApFq%*aei3s;$;*n77J$%IU^CdmDi>raMOTKAMbHS{H>CxCS+| zYRu;F9wXFUhvY%}IiEeYj?H6p>#L5dgd!@FgqT1L`C=EIOF5Ky@_au6$r^s#^0d)2 zc8aT|(V07;l2xD+%K;b2=ibDhrxxr5k&)Wcch4ROYLE$vh!jJOi^oGJpaxIBasY4? zNiYLf$2E|aSw%UFqhdawsMI7R)8Gach{j;XHT?<9PITQQR#jKkVzr#hWT)fl_M8dn z-V`Z;>|~Q3wGod4AKj^#%Y2;gPe^h2cpHF(GbqBrY?7jYK`1Jciqw=^beS0uZQwEeeMqKGk<3(qNSOqSsZ>c?hDlBp zW$KhICBn#>S(349Tq!WA(z$e@=N@T>t%OXxJC|LbejcBRvT&A8TP4;MAQpvLM-I>I zQp2XuvzytnOs8x_3v@`wp1uyZb(hPgI@Re|qIc}W*e21V9F*}f%jewZ=m$?9qYvez zb|iApl{>HwiOsNW8%uHicmdKNPgzuRuQY-~oQz`^t6YC^VD~Bc{ z(E`^#+SuTXP!C9oBI9K`;~X(ct`#vdo-=z^K_p?6BgL3V%|O>*|LVU)MrUgR0;)<@ zqKHsz&;~V9H0^+x!IQ_i;m+!-$S zn(9;xrdkPTB%?E1UWJ!xaai(Hjb7vZG3<8Srm$%#(kHzdO35>cWIl^Ns3j|9TCJ=~ z4OvE3q(N_EybAW#SKkFG02Nxf3%1k`^tUo*(L3sq;|x3v|KNQFZW1G-1BNKbqMRmF z8J00g9#jvI1s*_y?CEQ|f)!FDCJ#wk9Buw@@UvS);6lG5umoyy@@%2W?8BN7oQtg~Q|ZJy7|a&wEg&BLDk z5HFB-{VDNTP-e^%9N~#;#s&SL zyp!V%@(zH92LvJFr|7Rd*N9JnlAIx0icTdxq7A&9E5?=H)JGXga){QzA@wf(O;Xb} zagdlSim8Zo(sOF9uU&u?Ny4RPG>$Z}vrhDBm(9IVHjoy`G}K;@BmgHLTh+ zqb3$Ndi2Il4AqLUFp}BHEAc=_%9P^nqeBs<=|VS*>=CTQEaO7?z>w@q!hkueWy|!; zi1e<1_2>VBXpO$bMe(U5sS{aM7Zqcz0!h_uNAzRHvir17Sfrow$>TDjxAZ+bql+_A zMvOIm9jo?F)i=1La%gr@!sxN@e)XQw#QW$n{46*~=eETrtHedckwEq+ks&O~v51_- zwT!GovoU(^+qif~xN>i0-2y6Fky1n4UkKi4Oi4^goJ4OG862LOx~G56d`b^MBto1; zXUPfTuz*))QP#;0GZNx~0qGHlkW5FsxCk{uday-ELOFF=I*b&-l)7Xt!3k;UC2bC) z`*X5$Xc3*fpqJpRW|e%{>i6n;`N*iZ-`xCSTI;RPAT**pOKuW(GXGM?X@Al+ABdl8 z^XO$e7Mo%|Z~EE`-0pAc&zJpQ{kw-h8^{0r%P*d8*PqVsWvS4y(Lefzuj-~Z#dK9yz9v!6^Dl+0Fgt$|(YliB9# z$@y!;T{rQ z6}m|2C*`dP zBR#Tmo+Yd3(e%irDphA8ii@=*5;7z_eLOo>02rXKq|Sm!8Lq^nycL}ROJ_#&L~u&e z7dEY=Br7DQ*^elBnMW_mVoT*RF&kosdw2wA%0j4iQ99mMVFUvSM zvZeQHWRJ{baWSz>g2*c7{ut}Djk9n|+@PSAh5j_k{r7YC@ zp{M4G6cPc1fQ7;msE%T)m84UkB0@}*8JWr`Bh`aB3S)|d0sYOS4bf%A|+Mh zj^jjCDXKXMD(E>6(Nu#)Eu4#jX0R_*Uzq??0d7;5^tF#q+bG0bVkpF_&8Iwc@nJpK zhVI;j7Yk!dl0l>}$)uJ{O@C_b;|^8KrlA_8BUTMeGF7Pycn@FlDnQHJvP-&Uy!;dN z*MIs;(9@It$?~0IB2OZV=CQoQH&oG-?I`7FW3p4Jhd%4oc=1>hjtF|U=>JUAb|AEW zTJYmC2s?%)7;09k%1kvl_}N)C*Ukj#=vNz_E4s4T|Aq@qb;i9C(0K%p3{h%mE7^p{y7 zsE`s_!9o+63V6`{V_yI1(?8zS+b@@2xj1jNhN>A%X1Hr5dhyo$ZT_zM0x z@@-^M%%`|g=AWd z7fY)q4MH)_Duu)zc-G_IZyiKO>8D9w6|aQm?JwH?E3XOLMAf3G-z4s{xNm!6W*kM2 zI^S8ZpdbV(5LK6&P)|gp%E_w*C>$!55|-6v9bnc z7l#bo%)TG-p-eYrzOV2pup>6~NKeR2EEF=JBg6yOkI5gB5v(y&Q%fpUSVd99rC`)( zX%ROq`ySn2t|uuW6q+)-M;n?DVO^L<(S!^f#VE#UmiY*b%&U&Qw;t(hMj7y)ks~5U z4@V+m_vj2&Owxv}Sy`$cDliM?!bwk)j@e?{$MaovQStx)fB;EEK~&WRZkhwx2v5%x zQ!a+GOg!Gf9W517si1Zs(<6KYGAhCmg9Gqjj5(SmnPwOT_Wu`)Ezpy-! z#mXWonauDQE%Q13ihx#@>4lE2!YY&$g{Z1%EpeUVu>o6hL7q_2dJdBmA(JKZCR+0` z#<=cHfhfu)c?&%P6NeO$Vw`~^sZ{91oE3VQ^fZ%Xgph)qfkPtMgC2>Icww>9gTV*j zXXea2VdMx2RS^;X4F3*3N8Z4TL}gqMpG9inL9Wvd;y_ zk_wqqGyCwcF4bvZk+@bql&s7}m?|(u8gfz$L42&`=R?kgXO=)jruD4DE3ER(E7`x% z6p^O6WnR)PF=a9Xnb2SV#b2N&Jv6(*Mb6|=LdwWOh0Qq<9b#c`wlaA=#sasJ+2TbZ z#Z1Xb@_M4EKhSIgMCa=BBt587L751&dcU&DeN(W;LCb`XZi_km}-`BMz~~+}r~Q z1S5;*BABhnbkKzd$|)s)4yMN#X&9E-GMyR}FbdO{f@W!&%0$-0!caQpQ1!foGkfM~ z@V-+i6s=MhK|0%r4f_g9)EB<7uuimD6gtJ!_0!YgH%Bb`^RfH+7soHPN;z8Ve%S{W zkz)R!_E^UnTXY=Q3yV&ZO*Z3nK)(^ZR%$=L?mzsZ{MWGa@N;l~totXso9dr_aR{ib znH{}l>@jxF!7((hPaL1W)9?Q_{^^I$KmYjf!Pdsj)_MQ9tTuf1+~FGo(~xP9Pi~*y z)gRyLw}w&Ltj%PYtQG5oMjJA^W_y{is&>XsTsW@KE}9}Nf*O1GzNaIdC}dU3BB%&Y z^pxJYDa2@DK+Qa%yb`Yji3MB`d&|ci-AYu6YB}4HxT9g%*dv%jVvleKBi;Qnyayop z(tJt89z%k!jjxMlopbidG56re(LAo1YwShWDG!UhI*^&eMJYz6D@M$~!ps)fY_X|K zS#(s(r;HCHEA*^fq?{F^mIVYnVtbD3gNrw1c5`3KY|~LA;>pJsQ4m^FmLLa%+911k^e$OhX2HUGK;$;Ynjs9+mz0C2wM>mM z2b4idt$Lc}IHL9-oaqHKrS$pG4(K7x#|8`b729*7P%0TJVY)?}DaWj$>4$rNp znw6=P`B)C;&K7ai!ZIs7wPkv=5s2uF7@^%k7s-h53_Qm85aNKOZ!tjLiqF7Y59fIn zLRoHPIWscZ(=$6W1|nHlYfMNoNE4X_NejL(gSy6MuAUhwGkwB{zNHIk6z!?ax8C>e zoAe*cK0&7}0ECc9>Z0(%UNDY2ZUCf?EGpGXJBFx07$$Q92voH-o>1J|JD-ckFaFZyEL?k0iAbaMH*fVwj!W3V# z?;tf*?Zt<%LNCZlT?EQ3qzqNe>VN3`ee3c zMo>c}G7$-e{^i$yjS-O^a1x?NVxc535VOlEl-Y^4Nqv?>sHY;aF?MEX`pa)mBKKy( z5-7*)H=zbAalBdMw51?Lam+k=K}4o3z$xW(#M_8hDPIqZ-Ny(4EcKLe0`|a#FwIH~ zVkeTtSX;-s;}UoRqX)WBvlh{$ggm1@ur6J1w<&vMx@QJ5ytlTsHZl+y0V&{2%wm(M zA~otMaD%B=?sFZ2U1*j?-NjNSu#;gBr5UtR2__|pOz{+_o{{&7K78}I&-{W#EYoBu zd6>``7sx8SioToz#ER5;1`k=>dmnOuzpB@Fvj6R;-yZ9$Z;oFb$-5=D#1&f+712ld z6D2`zm9HTt0@p8_OH^KeM*>?NdLu%pLnK zvMWauzo373%MVZS{zLop_Yb{3`f-cioXhS@4^NLwj9|~%V|Lp5IBk#G1F5a@QglEA zx;9={c#lLh$}WPb=EPsLQPRB&=V^Uien)_CCd~tO{Gk=ZXb63c*zs^ zM)C~aQz39oXY7a-@+_1}Gx4G*^(ed%3pB@vmgOnz*<*F@@PRqd9G-*PQD7!hDSfJU zJ-41~Nz6%M@MYW`B1p;@%E6d351|L-kv1}#j3^~_$|6}yLvC4?$eRF}5IMo#k+TKG#s$UWh?$vE>=2E?!uFB`4FCf|q8tu;F3sr=8x+i*A#h9~W z?Oa#H0R403&xu=|zOpHZopnvQB1P1YIY#W;gX0tW34EQ?DRCa^kI&=syk~B#%4%d2 zY%;`DOYNE6GhR}`5~-#6G-d>zK=YoX(O( z9Ewb)F7%T~XKug6yrk{YL<-Rd_lEQ&EWIROYy45s znFYuJYpfzrK_zd9Gce(0b-p-FMxnfrD?ISRfbbDMA|fNWCpNGJ_mCt*q`3@N%~A6R zNI;$gPtHYnQkG)VY$@VmL&X`IQ8Jj3K@FcWNBR~%dZv4LhRaOct4yWjbhwRpAn;cg4S>sfiM&tjVsDD|SWl6F# zv9(mq%-tj6zwEuwIhk2iD4?r>1d1SqJ)j8t1^BL-$1dCh(m2MP%_-eaFl}@Hl@VFFz@4i#95R`{JM_lBuau9px`7x2P`(nHD~CKIgc!zQ}Z_4l4S{i zBz; zZ`(vhn1=-=%S@KAbeVFz#W+V86RwNfUTm9qoyQruLqUM7^kSB6ih1n9W%PB7F~-_- zKH2-zb^P6@PsbnbKdip|rhJqzPgbg#h@{NQ1Up!`ddd>9=Xu*-&fBH=r&D_!l$AGa z-|Cx9c84i6UTjk-(>h(A#y?&95x93UT~X2A+u`XS{p+90*XKVx#sB!Sy+luGkL+Xj z(HZeByr+6s?E4z|p6BcO(%0xxxGu%sZq{32Tug5iplWV)a!qE{iu{m8L@DCI%F#K) zrp!ClkKAT#RXkXo44y18diE7%lrkz+lCH=YF;;AEv=^&aW01pmEjAi!VKW*hL)IwF zePaw)mF<#1q8p5!fxVB;{ zbQ6lP3g*M|0_>Q2)?FBhh;7B|nKWcE?LD{MH`D4v?ah|`BEN{#aE*{6rF4KNtB<$V zCv#Gi{PjnAwX!yoJBU;Bk8`=Af6dMmBhHL>^Hft@`+mt$Hu`GJ7z*^v5xZ~Kx?5{) zKKRHSqa1fXY&Mm7nS#}A3eRL9i1k*wGVhvFx@Mj;TvilM*Zr4ENsj7}P&J>*p;iTM z--0P`E?5OO@CwQ&++~?SWz2ipaPg!Jc`-l))$vMsu*Bgl`kNGKVqtxk<&Xy4C)*I}pW?z_Xs6r|o=!!_s}MH?-UYv*~* zl64{FHcE?JV!XdzFXvSbR*xG{Yp#B5sZDXojbo*tByul$pW5Jeh8~1y!^&p5k{2dG zhf3u%RoGopq9yc__Bk?WPHMnH5$vgu0XPSq18z1M_o!Q3Dt=zX#BtCpY#MfQTdGfU zSq_%;PNdbupawIB#-0huglpm)${?gYu!hXpoSJGbvWy)0s=TCtB+`sux^}{Fg2_o*V=0!0p{CLu1!D{Q@&u^#Vp(K&pzKx+ZVT1kzvzr9tF?_2bNll0KR!MG-(Rjd z{E*foBZ7NJ-`{1gsCm8MyuI~!ihbXEjy+P!d?R0o0R@@WY^iFJ*#sPrCCo=j#Xy#k zQ{V*Np-zg1@e$*<%)&H_cGCb76erekUQfI_Dyy*8-8;K*&vK;jD|FY zC2aH_Eu7xT{8v%>C^1ORcrkxp23#dRc2;Ohzxcox5G)-t$_uh8O@+2tF#mr;|gL zwHBL|hFw?_dpJgT*GJah@x!-x|6^V$5qWldcatSN5^nU>{lX%c$l)0_syGp|mm@#) zo+IploJ~vB$#qNca)(B)#&<_`Z|2D)b>?{G?#^QNGut1_u`cC)j^=){LoEs?!2(M` zYE8XqKnZH9Jk^P{tkrkV)ndSDW`)(_03J~cFc_uC7@d)G7j~tiG)x6CtIYeg^~-FW z-C8ZB9rpcwzmA`w|57yh^x$<@>?3>c5rsw>qHA>=$&!F^+kAQccAL>IqOfoYNTbXp)ED6WN8gK|4Oju z-r0kNY-UYp^28oMaX5}#H{bUewxDhM`#P?{)3AqwE~g#ZdPbDdOTP>&oBL2gd11U+ zUzm5Cqm_NCfpz5hJ#keQSF>BfK!?;_Fna8Y)p2ne=#2M0zMaj+wqQsgY@^P^MdOSt ziIy?bjx^;6=%&H_g8UP^TU|I?j1tjv?6;$o`*X$T1+(J_+xKKm0SI+5OoUvj zmR>|-$FkeKm$`bPCL&V^_y%8Ts>O10Z$*XjTfS8JmiZJ}!sakDv!W!z^2~SwHiJCP z(m=t})Jw^lVuG05?kwG{6rQ{uOFp`N^184%S$wpy!B)#2x-wdRK-Q%gclsh zg{;;ZeNjDd*wZi9|5W{<&cAiGx!jejm1{BZ-mG`a)l)zPdd78Rds~<1^N&w|_wRlg z&kcGgm#M_*{xaQN3tBA4p2Kss!vhXKaMUtXhd!V6Pe1FIxXJ^%UZ+t;txKfiok zUvbJ>6(enEp!e*2gTHc%^EKakL`C#I_DiZN@5Dh5sxcc~bB((U&k&DT9z}NL4uPbY zL&*SlVnCuqrAHXtZE-t#HF$ypRa7grReZ4WgUN_tSu^M0?9)=%SPCU$S|;}ZGj{~) zh&DvvS3q9B7{LNEk7Oj-9qf8uNq}+rF*Q?c)sZEy}F!68Eq!+!6Pywds-*D!OW^Hb^fj$ zEYNqU$&ZpD2$b}i6U*dnK9u`beYi&*7u#P^Jdbr~j}cl;8(9WQLJDc*8}XCH*+la) zFY~d5avRJPVsCp)JA_<$dCRNFMw(G>bXqZ$N~Y6?)uhcV2ZkA7NivP(ptKI}8O=w# z=?HHu90{XXkWMRpurj$-`fMq7m@o8+&QjWB_w&Ov587ttw;wZQLJlD@OhLt9cJEeI zif1j+tXZ9mw4xF+*G+hj==18G}mm$-R*Y4~{OCOifmmGK8%!*qTQ-&fG za%wmf(wOe0+BDbd?s8-@ER_M9BMxCuCUxt+E*|$K95j_f>VRytne#z$psvi%_%c7v z$8sw3sXl(ZW?$8fC~hm_Le|s?<+^WgE0+Fz7uWy(@bG_q{Gx}yzMnq^TjAXm*k~t= zM}J?tzrX+HH;lh-D>}E!LBIR~KmXnT@a_Ho_IZ4{>~EoU-wyj;kx-7zk)B(`ny=^A z*H_E)oA!64O18_`Uqg4v@1&px(7>h=z%EA55#v$#9xZ0=cVdB|W-vN-@@g)Mh6IM> zzyW?N<$hXH*~7$6%T_Rz^1b0N%%kMBY2L=+eV>WB)Pis_kqIAO_d>u--l)mK3%nFo zu)<)*awvIceadY6en0F{){>2eV!f2DaXrAQRr-;9Bmo1aBl5tUtt?*AG#aYmK%sM6 zRfMDEC&PEN9=gyrj|&(W@0-T7^x^fQI^O=hMoXF~K4SGFU36mW)(L1jbLM|HMJ?L|d$$_}$iZ z8GyQBooZWVttFq-MIPJ<8?YNaSkwJD+!?ZDrUFrqx!>hL?CK#|w@NoF>`KX+sT$p6 z=D`f6(Tf)|7P8UZ%1oYESZR|@WI<`tUG|AV($pNKlv1ioks)P5;uspEW8Zl#^)O8} ztp+$r2f1P&v1?G>fk^g%Al|ME(;y!uvkhw)m3%1uQ`=r)Kcl&~Y8KE)t2>Swt;{E1 zj#5cG?$?KP{Ym=Iw#~2y`K&mktv%i-Ki26GYw)`H0sLUOK(4f0Jx9;nrLNf7VJKGb zU7tKZEB{G(%JP^-25g$!a(Ac#*YpMWNHxEjtNK*kIXjnXgao7*J0m?*%U0WtKD6VB z{-yYAcA_LCyidN)ZN=Pi*FCz&k#-_;wp_RZXBBfa-nt$ZQY1?fv018yPhksgrx`O( zxOQ<%?vd9RE4;H3R?*Emd1?5NcyH-E?6sDk*NMx9XNjY+iK1PhN2oF|dBdW3a z4X9FP>Yi@O5ZbUMD-7LmZtZ!6_P_q?R@Z4B_4IJ~_;?B*;J!mC zjoWGMto(+&W~LI_O~`A;1!is)v3Nh`Ds_4F!$QLbtfTl~tw|=Uu@us#Y6r~3DZ&t> zTyms{aG3XEOt)^b619ME2ad4s$oo=D^*YsQ9>k!s6}xMiNzl`Mlspf-?LCw?V#Xkh zfZ5f!@iu6>Y8_Kv>78h`9$McJ7tvXpYj51AfYKr-U;+wJ$%EP{k|Q-@Lb^BI!k(_F z0Oq5hIwo4WXQ9pJN1M`@J`}x=>`+Jc$ThUmyFnR9&IN~JYx0~HurTgKXAash1w{Jr z^YeAVhrgJ9w?o#zliPjkL?K6oh%MJQzrKErFMrS9wh#aHzxv@{{p>mp@G3R^QhfOChsoY?j{8L|&mm7}Dfa^+*dcF>5&RezNmN3h6=YZd=NfjyvN= zr^$yh$w9_sG3%PSj|ki4@Pbk`vAB)B6)t0X@AZjUVy{IE7{GA0hjRSBWkX7ICkyuy ztJ|nWEjzi9M=Kuzb?w(mRhb5nZYpk$>EMS43*r(49n1|DP$jGR1xs< zm^o1=)Y%@j{3f%k$%~HY#qwSKN$oVvzd10Z^>()nB-Tu ze8K*Q5$UD635`VaGW+3l`2O*pccE%WJn5OiIih z>!#J7jF&PWq>}lT{p--6%~QQQ+&>(gtL!{xYppzVu(Qal$q#ue-;cs!<(Tbg1+($aki|wZph9Y9)lr<)T3H^-$t{Nuo4p(g3Juip zWDg%Ip&EMRI06DAM+p^im|JZ>l;xA@c02=+lnk=Lk?gUJSVulb|0RROa#IynebxAr z7&HTRArm!imf(fOaj14TJ@ELLapm@6p-G&AX^NB-+ArWBCiY5##jLb;oF;=T1LUam zi7+eamCgdV)-_+xIiR&j%-xbMYhXi}Xp{eofBsk52AwuNA|Df6*W9|YOFIGz`v(4K znX>Zh<++8%0L;UYeaCiLZvDH(?XFHlC%Y|+mSeC+PDQGMD=qvq@*$E)fiAcZ;EsB; ztPLwWOA#i~(~Io8%)ic2DL)#J8nN^7)XhKV{dduoMG zz)?uEuxg;I%tHr_Vc0x}p;IgQZtBf*Zlnx$Xgp4S4twkMhScoTaP(jw)oiqq&#M1) zeVJO`qfPSrWxBWV;4Au$-EDM5@x0D<{(CO}9e(rs<6r)tPJjCsWjmHEBn2{@^@wrc z{N>Ca&tKy8%l7B(%P&8EdwTm1pa1;dJ^k_i?ee#mmuV2&{pOK3Io&tbVV1*@SvVXY$=}dM z#+o^pyCKLB_f4@;ByH4G9q`zZJCsdcJ1ej+r%PL8MxwwP+V`G+RZPG}kapvdT?O^D zOt6k`ILuO>0R>1D+CjX}cIo&gAqO)vW$DeV6ZelL~t2RrqB^2oyHsrfyVz)G!1G2CRz=v{22{he6i?Bvk zvdVt5=fCKMks?d=a6FCjd7pn{_BTGQ!{5R=h7CEiSgWkC$*QxB5nW@Ho^{g^yyNK) z+xdUl->a2VCHE*7%@_x3kIe7rqsM#hFVd3&aAR3bo8zVVQ!$Z5*<^!Uyqzk%9KE*P z>Sj}GWtpeU$RP~n?#dp>4y0T`0n@f0uf<7sKZ19d*4SUWI8c%WR?Q(xmD%jXLmrpy zZi=zx+Lb*r_l!N`3-}XNiZ8_jx@FvoNDOLR*6nrO>F!m*?80?ZY>doRlaHK<`!JCpc3`8Y zmBG0sKB@iyeur|VeM8K|QC8Myn@)^7hwKz~XXun#YVLUG`AI}*q@nNH_TqCb=>bit-%_ZX1zdyC&t+JUjJ8nVQvn^ zC}S{h-c{Aa7wC^hkOlPsJdnHL>NHegp{W}0n2*K*-!(A?Qq@o^o5~bcfT~B|R_{70 z?<7QI;=$@iKGbb2BaSE)7A1R)GUz)@zMy?VJ`e#6Sb_nm@7OvA2yB_F?B~osj?q1&ol_twD*a7|XRs@}@Rquy#?a`wM%{&;+_LoExMw3L&w{8{XY*pW zmN1!aF;>|sad;Vu#7IdbMrLdgPlKHiZrM(M$tZd zx%%C=nDE?t2Q~{kmU2??W-Ff)?K9ozE5j?Dwa?{_E4)%3h00HgY4*g(PcG z@0UZr{DMFKPuqX|<(H>F{^|Ye&rfg9?|HhsE^D6G>-92T_IwS?4Y9uP`RDd^wd<4Z zY3HF!xzblJt~vpdhbT;mkxEG#RKIOOxXX7CX$q|A`;ZOU&9v;U;xwD+2Axyah}FiW zSjrK(@9I0RR^L7M7^AD_z9%=S%Q~0draC>|7X!+U+>jTbRMv`>O!EW$P6%li>lXFm z$&s~AZeIAl)a%Iz+CYSlVLP#zl)%8d>}tX6IgmM+6+WpYC{Y_QWC)AcyRL(Hv;G1| zW)hN?f(US$r{%7x4AF9#$1y!nwyx|sWNA)u2RBP|RaB_T=4z*D+EiavI&2^bFS^|e z-!530J=fTth)WTDE=KI~tM0s5(OXPc?HC;+E;in*z*;eKnl$G=M(@(zv5E}Xdfry- z?T1>wo8Xs$_kFeJm*Qe0^p^F?0rdt}RuQ8pYN^FfZbzue3Tw4m>Z9ti)W?UW-Ebkd zX?kCxk73)up0_m{8z$1k$sp!+B-tIwQH9OrTU+F_h0 z*SqcCtQOV-Z*Mw*(k(AwVs0v)$}Mu=hv!<`Zi_sJ#z>q8o;N{q)0PwVVm zz20;)r>u`zY=<`F=+U?BoUboq+j1jai7p$-k=M7^r>Czp)w!6_$ku(@YxLB}ejQ&f zJJY5t43`R~z-eR#dMMHc;%XSq8yCOPn`1cXtZ+w~(Qe*#)}hF->sZ@|yUN-mZICcxp9F}CY^f#7n4c1SU3 z-5;ED;mmSJMplD;&-#I($UStGUVxs%GEzf1tPjQYoa-(xX%?=ql&+C4LrJf8`#R$- z$G3pQn)~Qok#4FEhBIJB_b>`{%QDAduJ^T>qgKdb!!W@C!vD?h?}`=Y!qzA!T+y(b zH!p3#cY0dVyqKG}Gz->O_%rhctrjO(QI*UrcH27#i|m;D{TSyyp7Nl)q|QY-Vf5@5 z_02F`JG6mPx7;%`24WBGP-H^S$uD{Axedit*b}4ozQ&$8WSi`YN@Qcqm50j8(rl^_ zrAy*9%=XQD-^3Lvfi`kLJCg`UtP$@c-ZIbP)&0GcH}L|ep;8C;Q=L;IGP|^DyhCRa z?ktwIt!o<%lxsiueNWWWj|MzwjAeH%BhzBs_<~n@BX^-HyjnE*kQ!h zV=ZY@MhZL1{_=${@7w2ZZ=e6^Pw(&l<>~YD3%2v)vTZf?%kZ`1b?@iBj`ex0=eF5# z$Ye|?OGjDVH`WO{!BTO{Q=~~ZTb5atk_GAPWT%Ufrky!ZjA31Wz`D@U?v1~(c}KiUm%!T?wOLzA$UWO#@-f@)xRg?B zYtxdg$!DVix`Ml^_4L3n8>McJt3f0TtL1J=*+3!9Su~BDB}mc`1rMMbf)?Z}?Fq2V z8fj7?0YX@;9r+uLYmR;Fv0vp|uX$BB?I>oYkj;50a;3Z)tMSD7QID@<|2d|}ixg#K z>!Q;d_uw|_wyp2$`I2K`6tdau%E>lsww7EqA_F3juil<-`b+W#;OT=y5CjVrlP6>g z=kE(XT1DnCJWu{h>$Fm=T#>u6DUZ2{^JY`HXKkl4f2!VSgetAU1$7)BYW^_ulKDEu zyYeZT?b{qz3Z~dx7iYVrVbwGHw}C(7o44s$%3fk^mwg*?&PiCz?;AcI+R)en20}P- z{}k=I>N(yhS%xcG;uGwzcrfJ3LCH||7;9g;S(N48$`5t5X&$}=umivJ^~b&)>-60M z5=k4?M)e)J=TsDvx+y$RuDNV+pK&i%pqWoIZBb`lQ(wi;8Z51rNoKTq)OY~CBLBqe zROX}CC)S@)4u%IC4mi96S14JU&2BEM$&uxK^Z9I(s+HLWw9B4}&lc{O+=7DHpKSV1 zzSxQL>1bw^HA@LUY}Cf=$j-~F$2Z?N9S`%=fvZZl7-c-6elg4qkHyF$PMgM-W26sE z7x3a|l_G0GGRtX<$IL!r?Ku#gIlNh`m}+14M$}=1D&RwmwA=C7Y&g!TOX#+NGtg); z_93W{tw(qa-xAkE2!y9w_gs7L{W!-s<*#UtF|Z|mrv6AR$a^KEB38GlwpDuLsxrf7 z0FESYUxIYUiTDU7a-w~3-4*OZ=Z!DqXQvShl@uW5=-T(JjOui{r}=PyMSjIz&5vl3 zQh4h8d1tj;TB@2YERKpKNt(ON0CU+iS3k|I6p^J06DAl`Al__M|MuVhuI%MDy|KED z3N%{yJc@SKjpM?2gUo%ZMmB&4oIY|Ki+S~n&Cfn$S&{SJm&PN^X2XB^I`k&@#XtmJIAh4r|(Xu$NAIo(3YCVa{X-M4`v^E`d!+fZ9cX! z`hbO5m?0|8?=R0!&p)r9U-~cC%hSvLIhXVKe(T3br$)E2Ut+&nq<4Dt+dNTqO@ZZClb+1Y7np2BXaVsxj|oWr;m*a>*V(CDL+R*T=B zlOijuR;?b)3M;5;-fRe?3p=7!C-ELR8Qq4@X$ORCPp-ly4)Z7qwjc$o&=T2(+|u1s zo+8kNZ5``AaJ}aBEtxVem)+hs^Wt;ESW3UIsg6#wf>Kho$N44RuX(9@Z=!eDRDRkcK)r--Ka$&!-*Vry2-eoVjTfVl<-^RzQ zh3DoGBm0^XCCknUjKt_1Z~gsckEJXhT74`JALcrI+Q7VR+56k6-~DbmeSe~;4xOt# zm(Gki!Ud)&gpSydJDxQDWV!!}&#Chvq0G=wg={X?N~M`oMkVt?h8;hY(}R;JRrY9Q zF(!Jab(H~%e(O&)vX|*P8LSo9VQ!}-k&$6$rFq_4E~aWeJ5k!y@ZreiI2x@2=EV(U zGox{$k7AdZC(e)kP=+ESTd}2jvwA2z7B6Jdld)&+0Y__=i_iC^F2>5N+C2t>yYgw@ zpEr@3-L0K`n(mkTk9RnX%~nF7bmMPUv&qf8TCra;|7plNPa78cfje~(Jle8P+{1d$ zEp3aDNRTK8l=r>=vhBJ>>E2{#A(Au|>I8k1Ey_>^Y;EN-^U6A`x*d#aCQnFq1hEro( zoJ+A2t7;8}Ww>MzNvmGC%zip{Q6mzvL`1P{8Bj>+fBV~i#@%HE+`~&w8cS*@w&a%E zmhpyoMYd87mCWSukJ|oHYoP}1CC4usg?=CxmZO!Z?8VRyYcLL-bc3?iIp;#W*9Dz__BX zW|`!uuDQ}F!QPp#)=Sk?!5e*NlB$J(a()9G@(dp-Z*{D=R~W%`}p|BJb8J}&0FIFvTB z>|Wn5^Ao@Q`47MR_{;wJ98aUQSl=2!meefE-$yP8*WE&wywu(4H?FI)d z?$v_~_)SEz6W#o3x;nj@m-bG*n9WUhB?eEszQg;~-V?>Oc+bAZdW}9}o8YrK%~fjF zmUcvc@$vaZ{On!}4{ir9cQ%hi*F3q+tpG0A5%z#e4u@9-ZxK|>=D8FGxN8O^H~f9s3wSRs&ZA{^jSdX?uvQjtQ*o=#6!x2(n9Cz| zPj8HcWih(VquACkz#%7D$Og?SYhK?Y%hsp09HDz62U9jF57OdUOU$JlDyNb6D-K9Q z)(+2bv8bAcqf#9xv&vDWTY1N)9D&?s_({_twueN42AGIc-}l(Bnv@6fp7x#1&1F?? zMmu+wGs=_nEX|(x9A!GR=x6#+jF1NogjJMci?MkIIACE6cL0T~g_HZq`RKQ4ii=Q1 z8q94n+*P%?;KrPn)uAadsP2*yArp$)X?uS-kFm#^QqSBK3Na%T%JjeetG}UE@I`>r zYbu8#UvfOhF0Bn}$OG%AIz2Yn=<2vx4m6ULWTA;Rs}8LIW5+TRjxDP#bkB)7(%1!gl3Vx+30yclW4L zNt&Y+t4TR>Cx_TC(giTd(gG&1t}&mRFAMI|WA(nT8ZXL?Y00X3;8^?q+M^XMjinZ! z&=WDlG-zDM{YIA_rx*ZQ5QVqsr%G3=PE&T^$t66h=8!4dD$o($NuZ=YcX>>+(z!mZX%dH zbL6c&+|$2hJ;Dx^<)feeS>Z=Km!#_4TYo!oeVmHsX=H;McSlF$mP(<%Y{xI zKF+o(zXS|yNaYO~dBNzuzG5{)B{`B{7HE=2$#b2uE||u)_Wf)jN?fws^CnOVVl@t6 zfLPQ{xkH1AV=gDeBsUG_y9pQY6}Og@zhBq4^Jv7;y?U!PKM_X@Hb&K3@U15zJNN=!j5S2@22^NVN%5hn@Z`Zh5VYOmqv3jyv z+;%dVHB<*CV5)`!Ge3Ge-N4d7b+)QPI1$FxnC@A}OXh0^;RP^9DYROwxmuCa#S-!~ z;~0)C!AK4jMa|28{`&T!H6u-XvrL$CbVb$}4J^f{(uxdOWh1Mw_j*C8@&hZpLZjMss;hj@YNo=3A~8&(B4iO% zqbBf{GB5H8rNKR?KClMn#2wAtV8t-Vl;yAtGgS;P14Y1Kxgf9X-mJA-e3Hdj=mnUl zk3D~Q|1vyYN`1`wF_x=v_T{AZVLm;Lc)R@m_s8S!@0Q+k+vX+mB))(7{N=}=w$Gop zum5oU_BLLh{_|z~_iNY4CGy(0^NwD|5k25-!YF=#}{v`Zf?$bpvRmSr!`@k$V?1 zxn1Q~G@}&bOP8s*nHpFuP-Zqxxg5Qf~DzxDX-W4%WGdsIZbjjgXdMaBoK1F zwfgN)r)CGoHSpeH7&H2-^cln57i*GkD>thX7H9%VN3$_(9|lm(J4$-_RnY+#VpqS~ zYx3E=QVRownNM!VQp}j}1|5K-`D`vTI4`MJ476glOnp7Xt`G(%^X7G;EwoSIZ!u+F zH|=g^@~OF7wW!)!yaF$RTQ)!uOeWTjO;jPu$a0G-nohC@(ZWpl2>TufO5|v_A%f|U zQw8*5)?C6!bj3j5-Fs0XD%zIY`(F2LM2|V`KAS5h<*YFFOR=}g0yZEuDA<7QFUHwe zeeaC?dAxwdGoHg?YHriurt)mJjzh91pcl<_4=1 zaz<~g6TknBkH2Zx>C*d)65e@4tfs8Zr(>y;ySo*!U?}#0kGDO}ZKcec_NCis`#I9aX z`}4zgCMszs-|yS7jOYi8VTxDao3Y!W`{@n)t34k}eMGHPWd=*4PN)YymbiLsRh&>3 z*2?0!m-VgpypERVS6eq5!$KD3Y6aoUd)g;7?R)8Q$|&d|?8&##n^4`3Ez(9uzV6?? zt$&fUxzz;wni@T&yd_(-%F0AF~ zZAsqeu^5Ik4Kx)-WtpnYtUaN*Ja%YrvnI#pSZ^4& z>RjlJ1wjo`pvLQEJYV2GrrMtF|LW%tJZAiE^ro~NhH*n^I+!pse3X9-D=G!`fy*5S zhBSauP!X2h(L2Vjk&)ssrrQbQC^&23&F(@YV3{0YxENmPG@owUL}d{yv8KA9!5rA3 zoxH%`39=URq!)NaGKWW0^&`fe$=v8-0wY@O%bdLIctajM?(2-{gviPJoSB{@jU;v3 z7ZBYI$w;vQO{8e_p}7MkeU2Pax?*BZhHB+t9gING2sc&Not?&V*#yZ?Q;@B8+&T=cH? z3x9e3@$2;;zy0y+`G5ZQ_WAYw?Tqt!_KrGij8*;J`x7tU;$6|DU=AT1!AvNViLi_t zI{`FzwjpjIt8R0(W3z{5JMx_@M$_mK$gruGgFTvkLiJ{OutKjlOe9Cr!Kfpq-HU^T zAV~~E_qp%UH*=y|C0IFq@aa>@I;Ab?l2k$W2=>el`iB}`Oa#7XgC@t(;!wOODz)xLQ$X!PF5y!^QC&+kTkPtik&ec<88!CxR<(A zE9S)<%#Q7x*InKVd#PD!R9a(gbZ|~RLU);#H-1s4@fLi@eZ*#NwR)JUWSh!`#wD(keL3{YCR_|I7ItZi@IE}=17onrC%VHceVOjQ zo4<3_WhiNKK;(eP-6*y@E$6QK;$rf7AfNpxvsR zQ?17~qJQ4D_1pIRkLz-=>JHT8+@qg!i_A{R?CaTiWw&^ty( z;DlDq4yBOU(VKF~aY8--oD2)GrQUNPjx>bQGFlmSIQGk9-+GUIRql}@2$|h_nI;x_ z$^>t;$;`aatphviqV1&Kp`Dr(v*G~VW8|LumXN?n3d!yg9|0?tjh<|yen9`I9ec#a zCRLI$)ATI;T$oi?>`}olNh}Utgv_zs8DPl8V73r&ri`lW%NAQ#Cd*G{XxHTxUh6-$geNz1a#{_nH4QsN&-Yvj1;{=;+Vzp5W0)x9B zxAH?b8%?#8*_LK)vE>u<2gpQ)G}_4KL<3SjI8W@S8pp}zk}wMt-|9hFmQvf&5v$^w zc{S9j9HvrXOO_9ThcwtwJy$Qzc_Qv?6sxiZ8mzz?ybfB2TY8aPQ5ab~%&TECl2{d-SlB|=u_DiA1!$-kVG(s#TQNb+P^rlZH4M>^RT0oiJnwi~eJOt5O7U9D z+>sY$AJFj5*1ZOO(5q|~=YhM_9eFYc9yAtXGqMm|&+{HJUJqC35a;5kw*r;tU?$ zOG7C#5txc>an3VJvC6FGi`j$MPt_h9JIb2g!JYU*``NF@I(;mxLQ@oZ!*np*Q9lp< zL!j7c@sEFH`n{IX`@7aJraz>i$1z2h8@BXonveGq@13>#Oq$az+{ZHO{irtf>pI{- zC5LHq8c_18Tyu=i7ycYTQ>k#4N;kUa!?N$Ej!TT!A#Qu=<6iif^QQGZE%qM03!Y|H z3U3_tX`X?aqJmPzXuPcsoD^W)vlkLFL>)Tp`A9t)d*Ukm$ovPp8oVl!UT8<*G4J+1 zE;;_um;W#wyz+r0gv`7OS0p{$&C2X;wzAmag#FR{G##cxM)4gbEnzvJ9T@SeP!3=! zLj*RnXV%4wm#~tXk7BzimAMs}gORyq>@g%5Wo9AuWKo~BACR-KC0Dd&cB<`gI!(tV zs$Z>cGNmeQaZWTx=B`*tq>u`le1hMh9E_D*h!bkW7=eknD|P<=pHt18<~6GIX&Urh zaVA~_8#zU=2i6$7GPA-a7P+M%AptO{u%Ikx=FH+Gh{BL{D?p<^roMxQxQZc!cotsB zHffm>psW2l2AjLxj&Diak|F=i|KV?n-e6}2Oog%=ttWtCgB;Ea?ad)CZkEM-;)h}% ztRd0%_s8qG?6~Z6t!Qne+sxTErr+l;6C}s8nxC zN1A1E6nXvs3A*8h*vXBY2kU-gzHoxijDQ9lt=fW$x@6vF`6D=ihf?m^04=1Nsu?Tv zM4gw~Pu$;gJaHx74XbfXel=X;TKcsFUe^A6MpvYlyy3dwn+%Y_uky15EAWzDRd7Mx zE^{Sy8aP~n*nz9;Wy|+#Y}hwTNEISH$h~<^&`|EbKFe?0{E>OEDFWs;dA+l8UuctIp}OIUxo|FY8Dy{GnWdD* zk_w_Tz?AL4o8v4-BC_n*-)w&~OS`=y;l;8Pxkg|g1*O$y_wC`kLrfG!c#)^UC0xR< z-Pdd@=META92Y{rFhYVV1nt^oVqeEKV~=x*OfbH1#E$`H4`eS#2&{=;+8b3fIMPkT=mo zo~Ucq3ktgz4%6;|4ZJUonQoxDNHcmA;io%OP!F^YS$#I~Xv!7n{6IZZ@^HSY)|Id{h3cOJzLJ1>x5) zsiOi705X}ouo6a9yQ&OWn7frbm8zCjfi? zFq^C#eKqUwxgLI7@8S*dZD2TImN06piZcXdfxRoA1e(`!$Z{WstERIscIN7r^s`9N zX^|W`QrnHiV?~*kxrbs;-A?tLo}S*|%kgyl@NjT5BrNhULg^;AF5ji+tG=F9;YYH- zs#B;zh)m?5cI=-cf65$A-WWh+u*9#GN7cC=4l*5-YMFDW-u#Yh_&YNR<7)O=xLHf4 z2!~2RtGt7K0IVn~B#l%Ijh&*A0(_V*U`K{5lQh=CWiE9>zZGa{orPB2!ASa0>#WFTB0vE>xrZZYSt{Cu3bkY!!n`0Fax2&-HgorC z1@4%TOXf)QG$I{ZLicI3L>)6wlXp2jL`0~owCpwelk!K5ofVqK#z?+Tda4{==yqY8 zc`c|Fa7rD@Nfn;g>Tlr!a`XQ z`xsjvZ?(M56X{ix!wc*dn%kudS)B*sJB`0cW6wRH8w@VSNV|^QMpqVL7z>Wov#coE>nYik0RQShe|b zHGhNN5H_-PvKF6hhEBd6=2mMR)3h%4%8rQ03`I8^G#jIA{sZt=W+qEeo>4-(>|Aca zu_RAWWW9Z-VomyQz?u0v$u=St>n_)lci(3M7e}- zvJEvzwe-`l!xJ;TI;;>2X_Q^AMEOl*O#!cWM;oMFy#?`5)X*lKk3@>b$Ha}w?oYKEL)ycHfNd|;ztq=?(;cRTI1)cv`3FBSdxwd%8vc3?2E*qI^aN|P1Hm*+Ck;ItuH%&#{N(C^kB!|mI+?q zg)ETE473ymT{pkMp~S%kOlnrNRIl(*4M%qgBeY#*ds+$Jbvq>+5#V6k#Fem$T6&MM z_j=0fqXNkgnzCj2I`Stan)_VIbm;8UOF?OedG;sTFH(gavjsB9bN1JyU@Yam>aD`f z8|pyxTZ9F;NxyT1V%vw-9tJnFq|w}4DRX^b`s`Y2`*z9Aq zI$1kJWQ@T?1Y$?7Tz?||?BML}P)U_kkb@}5N&TR4A3z@?eKs9kkoPPOE4S!@^@VL%(yGoc$AwN_~ns>x`3Xj6{#p4D${X=J5j zH967$>bL)(*al^;m_j}w}1V>KmO_U=YRh?e*XOO{A0Xr*MS^+Zd+Wodfv}=d9UYp?Qa3w zT?tN>)gU1gkQ`z&o8{~ToW&TsvImm|g|(LIX$&HT>PQ1qQE0bE+iE%D_z7x=L|Pwp zH=kQQ;UqsPE2@Ku5=@}G*+MK*Q6l3t@--APXecic(nuR(kxyQ|ve1ZPcJOjncsq~6 zjC6DBRo}p`(gJ?K{+8oQcv(Kw(|(O@>mxEE$GUIZI^H;*N-OnrH>HvsIDv~`#j=hq z`w(H4hLs-7l4{`qLOQFzWLnCEl?aEtq(?O4((>@BFSGH`lnD`*wc2mc{DQ?25WEM$OR_XT|FnX1(Yl zy&wkUgwcz6*fI(Vwi1@ZGf8@LoQy|L5Tb$!wxiFJ&*=vg9$P!k(}vSIc@5i!1w74E zkGV{v9?izl_GVU<2i4+Dwe;$}WaiHB>a`ZIP2P&dZatu=ve{<0QPCSJ>Ctn<%^0?`D(z6rY zorUgZUOhE?94oSnzF%@|8NJ3>2IC3@&1lPHhee1SJ(XmTT132p zXM(U|I1u;5WbVufTz=*AAd|{?6TV6Z@;fH7ByMyHRC)b1{K2#-O!U9~+kab16ok98 z8kWEq(u4G!3>ul%poN@)B3jV03fYM_^EU@>OXaC@uDfsz!IBnYt!k$|4_B`J@*bNb zEA0;UsJ0EAgRW5!XogldVH0;?w{kA==4rI5$`LNT1FsSyE#j5_3a_D>$pF*yvh_Fh z9BB!MIft|-0|Kg#w77xB?6vT%*(3CWGGkI-s_5_!qyHvUqs9AQF4P~T_q#0Vdu^Iiyyf-_+j`B5mmIi#F z0ckgeved1PQb7joW_Ws0yeV8+X>@C465@*IeLKg?e~Fi|zPA?vZ_D##Te}8&&Dgg6y6)%e^?X6SJ}uihdKqEKVzkQQvtjZETv*sFJLvdLT~&?{RKQrT-&J74A!EVb{ct%;e+Tt45OHvRoB6^ zm`D!dm3pxO(#eDcX~ycZV#TW!W-WzktoOtRRuIWT(X$TMd&4*AtKtL3Uvm2*{Rw+1 zqnWln_t6J9j2rjOwr8(jSnd!%$heNO;(%<I+C;ocG;>!8~ZV0C_6APx`n$}tE=g1jmB1vB0I@i-M*iqFREe$y8$J2KlCWu z%`UaM?bZ65m#?iXiyfx2A$n>kBQr-Nx4w9v%L~UB3>O!}tYlYAEM zj!ZCMW;{_o)PrKEz?}yxv)%eej3A58m9)l@BKq$?D=uqx`%JkXVEOw&G25knYRrjbhqcW5=F!Yma8 zP4NFOO@GqmO0sO}fqRRHsu=)p&N&fr?|u1_Og71^#UeSYQBCXr&#KlcPAZ$1`EC;t zr*U@xW~M5#Yk=SsBfuTYAS$}|_aRo-$)lhY0%~j?o&D6{3+Zu@Ek%%;V;q}h9S$|n z%50id6iBR}HGayAZ$H|;*|Cq!WkS<8)^E12g{Ki~>o2FO(l!K5(+o0fN!Mt9WVm3U zJDVdSh!t7V$Tj!54;?dej>jn~QcKmL+7Juc3*%yJZz#ozMy|CRsL_|I$K9Pd^pTguO+)AZcNek2ZYD^b(? z6?G>ZTBw(upYL>(ELZYOpRT>BRLb#t;;$1wBmbRLb7X=_(%K)Xj9LxJOo;w}{BQry z$aEVHA%^M@b`4hn$Ce`#2_})!D5bpq0BzcCr*uDAJiFK0_SHVpe$0f)+%~mcmG_z- z`}e&5fXHDqsFmuicvbBfH#0RxH$Ad`2KQRLPUI{2Tys%2S}0EPn5t=@H?S8>s=~~T z?;rjnugvewhB2KVRX^$vF<~XD$k}a{I&D=`FU7Z`{n%^5#c|L1J$0`@fq*^Ax;}#0 ztiTE4Q0f+Fr_q+(&k-++ z*<%&YRz%5AyXQpZ0Fx6;y30AtZ&r6J5A>G7=$2tSb=)+*Q%MEfOKrHx<@~kTUzbsK zIHsDn)y^xu*Ipd6_R@W-2ZXeOEZnt!zoHexMzy z1b}HA1W7llu^Fl|BKJ4F|6L`h!58x<8#I+S#)^J=J%9Q68I#%)T-G?57px{BXgywj zxqo@j?L!|m&C6{9rC9Fm^kFinE!Mv9#Iz*FQnMw4)*f4|Jamj4+EE*c+&PiUsZMd~ z&%dnv8MhO9*kzUahS=0Rdh=kDVV*i(u!LC+ce`fcige%<=7^ChR-tRIWYe&*RIRzu`s1HsnHHwy2N3*%)eB|7xYSgo4 zSKf0UnS_9pbgx+sF9&aabT$Y`|Wem6Q2qdS@CLP>EA^1@H75>;o3&CpL->bFh&o z=oxyJ&QdRg>;!+oNz`uMgO_lsW?^98SuS>)-`DtcY1IRZ{|J0*qM@^@GOLUgE;<@j zWq0%+IDS}G6n%VG{v8wKN~ggsoFh0}V_zKz6c1nnCVgj_dsrLFy=Ih7!o~3HHDSBB zRGy(Pu3rp57T8gpNwYHE!7JtF;XX9?%xmFRs48F#)DDMaXy$04?fW5F4kmOi~aRkyW!&dVPmixd*aX?e;)cJIJteBQKvhOTaL2hQ|hGc0kd`LIo z5TB-fDsC1x_YJ&Z5z3ezS|Xg&Ot7d3&_MqL{S$hX?&(FOVM2p-BX4jwcFRg-jmNod z3yo$1K~MV3K2wr)%E;l(qMg>$a>C3Q#~Oz2abM!&d|Hh!+!tTX)-pqLTf&;%(>}(- zjw!(egt>?;{=FX#5<)OE~pSD_|>Icy%(sK-91k#s{J7Mt7M`o>(U z&Z%BnB?3ZzqyNq5@H4!@I!xe@{gv~-QeL^@y>Yp)88lSmP#(N1Z-g<|PmP~UpcU*$9ZCq!B20jxLb6I^#Xio5|Gnma#NJ>R`V;b1 zqhQH1bW}e7Jnnzl+P2+4#td<(SKn@S94twB&;8?Wy4@|uAI%n6v7b>FBTlY0ur{3+ z%e^1>2Dw@D*8GtW9vL=PZWebn%e1kV1@Q7q-Tue=>GW~e8y2^x)kEd1-fitowI3-| zwc(bTH~<+A$q4~2)D-qS_KzIb-GSyPA@!)3Xd|yyE9{y6B)pVKHOwt!W}UfU{5JJB zsUcQ&i?H6cU*~n(Q8EU6kvEisP4HryTB^+&qh=gO+{g2*&lyMRUZGta)}<5NK^f@g zhVPhv!>m%9MJ0j6C_ifTTw4OO=z3g!Go^=u+zoCzEtkvri}u^RjX7?|(PZMnN1688 z{D)biE#^_PS3-jtmQ|Jev{B0q`_(?MtQKcB_I0IvsGWU(RUX^@I7Vp}VeXHp&dEZ> z5-ecN`XBv;Ix4>vf35vQf3Q$9J-F5%D0~+6ru~Pu&HuwM ze-VB&eG6TMttMD*T6?T3B%AV`eMi1wegkqC4&JG&+w4v2Y)8hXQ9@1s(DF8(RxU60 zAoQ=Ovq>c3!6^hq$kHC!+2%l}5fI!c(n##UhhSotrZmI@zB6~KoJ;s6kePc9=SQ>l z$(JwPY_Znj^K$y!p=Pu5L@uhPxfZ%J!ivl?g5(a~Bg`#C6%NsZ0FkD$`e1#h)#`S! zR?8)8q7GPbE!yf_?Lo3l>L_qJ_wYBh4^^S`SmAJf*$Su$7|iKO8#Gg6*d&{9;TEBjL z)ccR$^!-==dOwc0qr{EaFqDV2LL-r^oZ5%(xKdYxnKdg4NTzHODx!$81OD!DkJ^=+1d^tV8V{?sTSc!%Bb{Zal0K66 z1k5}H0^owWz`xk?h4iwLP1uGSt+XH+MP)gTJj9La#^#3T(N2B+Q=I&M zYmH$*$Xw9S9QkJBt8=mbY-U5gVZm}TvJA3DUkzM{Q@rB*1r*D?Ud>M|sHtog5;Qh{ z1VL3&rgY2tKKDF^?Z*`;=PPwKG`On@gS@hSLms((eB5q-pSRy7*68yX<31)1j03=S z>pzBaI~qY6A3HX^v;H3XSlZD~&*#^_JdNA&@#`J%t;Nf70ET1wATUlcLjXrFRF!s`;DcU!$hMUR~h3O?Mzm0$GY+QHNMq{+goo^^D5%; zIb1nW5{|9ruK964zJ3%lTk2jMnr+yc_l40-Fe_)0gE}w^ewtWE5ecenR`0%AUja3w z0&);?s7h5Tn$g?+{QCWJ9k(}o-{$f6ak!SXF1@T(m{?uv7G$cba(=D+o5bQ;nq{d& zcJnw#Tg|yH?d4oI@ONeCh_TK6gR_h~N&2erhxlqBCZt9!S`n9$WK}>#FT9wYqMbax zGzB#!&s-*6j(R?@(7MY4Y=EG$gE5StciLx*7k6*&VdNuvbu1N6ils!B851S6g_o1< zXYD6xFTY8(C$8!HeHk0??{&8wb+r`X-Yr`1acQbx<7l=ogs56} z$L2v+Ad;0h1QF>%*9?Y*@4dEWAPElHT|SDEjlq2*xQI*cs#!`3yaqm^!n7J8XK@yW zTmofB;flBe1KMP}$|!dZt3j3lWCg2-Doj8}>1!-9j@kTC+MgcF^E3m)q^gkGqHQCGeBsORPldK>~tL_^|rw_y^~cEkD`n zIvlkc&gKAwSWssfkdS}L`lT*bd((c>U`IA`IQ`hoeIeVDNDP}mZ2 zCKt$6sG_0y0hu&D3V*Nr@8)lYZvJE#@B@8>AI%o?C&S_VAdJ?{5=_P4Yy8LB4qxe~ zznJ|`eEK!|;T^xZMn=ehhO999L=?BN5jZVYFR9LM}C z>c6s_v9c$aAl=W+&%#nTl(*?ItNX`WzkH3=iG?CTl`w@=RaI`KL;C9a`!ZU@$~hoZ zE%SPrU%OhD6)@R{+3rjWdaS^S1J1HYZ{?>BoGLM(*{x(%Pyv;r@+eA<;|KE}SnSwO z#j&2pWz@?{Jv~=a$I`~_>DUQQtE!nAe~0~xumZ0{8p^|M>EP7ge`1{O%Q3cU# za;lGFit~`I8C^g@v$Cq74ds`tmqJ!ev16W@r*?_Hd|CK!R-58Av4|JMGjL&bw{AvK z2o(~XZrQAp7y66o$-=$4zd#2y7R3`>zs#dcV` zHvEX;v8?UX85b@KWdr~NR>V`i)HdgF#F#PmAJT7_E?a~FI}krceLd=I_8hTTTzJ}& zH_;>)8mh5>isjG!^^^aHfB$EUnIor;G4^2&HY1Cunl<%MmSy_F<*ZLUo+s`lwcOlmWe*{jwsRG62q*=lwUF>`9pI-J%$ zq@G#;E+UdXAdrNT9l(+nN6oYi5X!vpiO>AB4u@ijt+uUPS#X9X*3%Bop${jMK#8?$KKymlW8bhcL6YKE(Ou22WSMv4IGA#aG@VqFxi7Uv86 zQ?X=s0qR6)gWbY;1`3m`0vuN5X0FxLEV7o7xvO@`WL;@6LPRqdmrMK8=OQtu7T}EGRtR3KJ)>+--OH1} z?Y3X{yFi#lqo0~HjOL5Ch%jpppixeJJ?i)2Fl#D!zF)WcX5;s0HrDe|r-rAs z90pL{(&zQQe|#{at-Yej`X~*GXZkB$b{R39IwStcPJi;Fa62fOPtJ|lIa4+2xM{mr zvJN1>i*FcJyNcW3?LZ;S3Y09sg+`mR57OK{p6IK2grA)aK{9H#venup2(>C$Enesr zi^r4o)vf3vT5s!Oo!yK{6D-WkiGo)}%}cFce0d4J&^oaDIhJ-YYv6PnQ5eus%6P;q zm9q$iy+mI8O#1}$8Be}kUgPNra)OqsnG^S_T|8hrs~Z;95TWcx+O9k52eAP%!K?Mt z^ZNYUH?OSAc5T0EKubrL3CB?cDndawmw%;#d#pBa=GjD0qv&rn10SMdbo3CETn9tHTTw>mx||E zoQcY;*h_*8g`>FX0I#fxyVriwTw(;Ai^9 zP;S-S?WGw<;yx^jk-3Xz_+;(LGscVq-zWb*&Kc+E4~%r7%IX^RdrLQY!S+&Wez3mz zqW&co)@t)?-)4J1rhyGOk!K@}A_T`n7-D(|VS^s3#i~6n%59&IHX(u}%;46dEiK>b z_%)624lBeUZnT32ZwQ{;ov!H>Q4_{de z%nk;*M0O}tE9PoU_plz-S!?@m!l`!K|J=3FtRtY zyPv|(QA@+x`APVkaUc18D+2B`C>-cjL2{Z6Si;H-ooWm>n^DK2!*b{BsMEvfh$Lxj zu%dfg7eC9{L$SzCX#qvqdB@XyY{v19`X>0a4s_|HniiY)=4@66YeHAnyB`;~SF;8# zq|xZ+!ERcKPJ^o=JL5zdqnV@ezC_W(m$}X@umb}q3DBG2EAZ!$|I@AJu3Oc)@}&-_ zBs>V@kR}STW$c^j@WcAW<9wpMc&D6-5KUdC3`=mJemDN=OKUubHt+*^t=bA-!+-OB zhCW$7wff`x06P4%ky2^`7|IOq5@>(9bZvi-Py>|-Ji%Z)f( z19f2D4I2}RlD%nsAO(?i1wTOZ>ZB~nIvtGGS!b3vwpdmu;Ai8DxxtIQ@c|dKm#)fV zPC2quOZchv(-Ludtf+IPUD)i=NuIK47LK(!tjV5Qd^xq3zy(;Cg%(k-{d1d}hRxi^ zFkNr;F*4N%(2CU&Yw|=o&32TOhDUGs2H7k$OR@H%p!ejUY1^jUS z!$QWzy&*1a&zvvF%DGiO!9K&k&-~k{pRN9_Ba0iF2T$P(Xkg501EJYv$EaH+X2sBM zarFy;PJ2PUs#3?mC|MSv66lp(JOMA_^PFo6HL1PIM)eQX4_U#CR-~*u(aaZkwNtJJ=yt{FyzfheJ8M7K*g9Eh^qW6XwFpiIq+3ir&9Ns~F+k;kYs z)uRJ>VmkvXIGlT{raGy5BOGEC;y|W7V9{#Gu7gl1rvKmm?Z0CqoNya023DUvEXbgo z6|@yP(+p)*4RkvXE(eL)Q~-)bcUrW=mJC}xI+1BPBxOlQWuh(~pM$YnbdHLrWWo_1 z85cx5FPB&eL~7j4ht;V2mV7JxD73

    Zo}wdPdBu$Kerl*iRNe1xq$`>>SqziCWX^ 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 diff --git a/src/main/resources/resource/BoofCv/boofcv.yml b/src/main/resources/resource/BoofCv/boofcv.yml deleted file mode 100644 index c3a1f232ff..0000000000 --- a/src/main/resources/resource/BoofCv/boofcv.yml +++ /dev/null @@ -1,4 +0,0 @@ -!!org.myrobotlab.service.config.ServiceConfig -listeners: null -peers: null -type: BoofCv diff --git a/src/main/resources/resource/BoofCv/intrinsic.yaml b/src/main/resources/resource/BoofCv/intrinsic.yaml deleted file mode 100644 index db7c7f9ca9..0000000000 --- a/src/main/resources/resource/BoofCv/intrinsic.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Pinhole camera model with radial and tangential distortion -# (fx,fy) = focal length, (cx,cy) = principle point, (width,height) = image shape -# radial = radial distortion, (t1,t2) = tangential distortion - -pinhole: - fx: 529.74137370586 - fy: 529.5715453060717 - cx: 312.57382117058427 - cy: 257.05061008728114 - width: 640 - height: 480 - skew: 0.0 -model: pinhole_radial_tangential -radial_tangential: - radial: - - 0.17889353480851655 - - -0.32301207366192053 - t1: 0.0 - t2: 0.0 diff --git a/src/main/resources/resource/BoofCv/visualdepth.yaml b/src/main/resources/resource/BoofCv/visualdepth.yaml deleted file mode 100644 index b911c9a497..0000000000 --- a/src/main/resources/resource/BoofCv/visualdepth.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# RGB Depth Camera Calibration -model: visual_depth -max_depth: 10000 -no_depth: 0 -intrinsic: - pinhole: - fx: 529.74137370586 - fy: 529.5715453060717 - cx: 312.57382117058427 - cy: 257.05061008728114 - width: 640 - height: 480 - skew: 0.0 - model: pinhole_radial_tangential - radial_tangential: - radial: - - 0.17889353480851655 - - -0.32301207366192053 - t1: 0.0 - t2: 0.0 \ No newline at end of file diff --git a/src/main/resources/resource/WebGui/app/service/js/BoofCVGui.js b/src/main/resources/resource/WebGui/app/service/js/BoofCVGui.js new file mode 100644 index 0000000000..9746f5d564 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/BoofCVGui.js @@ -0,0 +1,243 @@ +angular.module('mrlapp.service.BoofCVGui', []).controller('BoofCVGuiCtrl', ['$scope', 'mrl', '$uibModal', function($scope, mrl, $uibModal) { + console.info('BoofCVGuiCtrl') + // grab a reference + var _self = this + // grab the message + var msg = this.msg + + var addFilterDialog = null + + $scope.fps = 0 + + $scope.myFile = null + + $scope.lastFrameTs = null + + $scope.stats = { + latency: 0, + fps: 0 + } + + $scope.samplePoint = { + x: 0, + y: 0 + } + + var avgSampleCnt = 30 + + var latencyDeltaAccumulator = 0 + + var lastFrameIndex = 0 + + var lastFrameTs = 0 + + /** + * Filter "Types" - this is the meta data type information necessary to build + * filter dialogs which get and set the filter attributes. + * in BoofCV.html - you can find were these are used when getFilterType() is called + */ + $scope.filterMetaData = { + 'AdaptiveThreshold': { + algorithm: 'mean' + }, + 'Affine': { + }, + + 'Canny': { + }, + 'LKOpticalTrack': { + }// LKOpticalTrack + + } + + $scope.diplayImage = null + + // first in list + $scope.selectedFilterType = 'AdaptiveThreshold' + // $scope.displayFilter = null + + // local scope variables + // necessary because service.cameraIndex is an int but ng-option only handles strings + // $scope.cameraIndex = "0" + $scope.camera = { + index: "0" + } + + $scope.possibleFilters = null + + // initial state of service. + + if ($scope.service.capturing) { + $scope.startCaptureLabel = "Stop Capture" + // $sce.trustAsResourceUrl ? + $scope.imgSource = "http://localhost:9090/input" + } else { + $scope.startCaptureLabel = "Start Capture" + $scope.imgSource = "service/img/BoofCV.png" + } + + // Handle an update state call from BoofCV service. + this.updateState = function(service) { + $scope.service = service + console.info("Open CV State had been updated") + console.info(service) + // int to string conversion + $scope.camera.index = service.cameraIndex.toString() + if ($scope.service.capturing) { + console.info("Started capturing") + $scope.startCaptureLabel = "Stop Capture" + $scope.imgSource = "http://localhost:9090/input" + } else { + console.info("Stopped capturing.") + $scope.startCaptureLabel = "Start Capture" + $scope.imgSource = "service/img/BoofCV.png" + } + + } + + $scope.addFilter = function(size) { + + addFilterDialog = $uibModal.open({ + templateUrl: "addFilterDialog.html", + scope: $scope, + controller: function($scope) { + $scope.cancel = function() { + addFilterDialog.dismiss() + } + } + }) + } + + $scope.addNamedFilter = function(name) { + console.info('addNamedFilter', name, $scope.selectedFilterType) + msg.send('addFilter', name, $scope.selectedFilterType) + if (addFilterDialog) { + addFilterDialog.dismiss() + } + } + + $scope.setDisplayFilter = function(name) { + console.info('setDisplayFilter', name) + msg.send('setDisplayFilter', name) + } + + this.onMsg = function(inMsg) { + let data = inMsg.data[0] + switch (inMsg.method) { + case 'onState': + _self.updateState(data) + $scope.$apply() + break + case 'onPossibleFilters': + $scope.possibleFilters = data + $scope.$apply() + break + case 'onWebDisplay': + // $scope.diplayImage = 'data:image/jpeg;base64,' + data + $scope.diplayImage = data.data + if (data.frameIndex % avgSampleCnt == 0) { + $scope.stats.latency = Math.round(latencyDeltaAccumulator / avgSampleCnt) + latencyDeltaAccumulator = 0 + $scope.stats.fps = Math.round((data.frameIndex - lastFrameIndex) * 1000 / (data.ts - lastFrameTs)) + lastFrameIndex = data.frameIndex + lastFrameTs = data.ts + } + + latencyDeltaAccumulator += new Date().getTime() - data.ts + + $scope.$apply() + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + } + + // FIXME - rename isFitlerType('Canny') + $scope.isFilterType = function(type) { + if ($scope.service.filters[$scope.service.displayFilter]) { + return $scope.service.filters[$scope.service.displayFilter].type == type + } + return null + } + + // get currently selected filter + $scope.getFilter = function() { + if ($scope.service.filters[$scope.service.displayFilter]) { + return $scope.service.filters[$scope.service.displayFilter] + } + return null + } + + $scope.getDisplayImage = function() { + return $scope.diplayImage + } + + $scope.setFilterState = function() { + let filter = $scope.service.filters[$scope.service.displayFilter] + // let meta = $scope.getFilterType().apertureSize.options + // let x = $scope.service.filters[$scope.service.displayFilter].apertureSize + msg.send('setFilterState', filter.name, JSON.stringify(filter)) + console.info(filter) + } + + $scope.getFilterType = function(typeName) { + if (!typeName) { + typeName = $scope.service.displayFilter + } + if ($scope.service.filters[typeName]) { + return $scope.filterMetaData[$scope.service.filters[$scope.service.displayFilter].type] + } + return null + } + + $scope.meta = function() { + let type = $scope.isFilterType() + } + + $scope.onSamplePoint = function($event) { + console.info('samplePoint ' + $event) + $scope.samplePoint.x = $event.offsetX + $scope.samplePoint.y = $event.offsetY + msg.send('samplePoint', $scope.samplePoint.x, $scope.samplePoint.y) + } + + $scope.uploadFile = function() { + + var f = $scope.myFile; + var r = new FileReader(); + + r.onloadend = function(e) { + var data = e.target.result; + console.info('onloadend') + msg.send('saveFile', f.name, btoa(data)) + $scope.loadFile = false + // close dialog + } + + r.readAsBinaryString(f); + console.info('readAsBinaryString') + } + + msg.subscribe('getPossibleFilters') + msg.subscribe('publishWebDisplay') + msg.subscribe('publishState') + msg.send('getPossibleFilters') + msg.subscribe(this) + +}]).directive('fileModel', ['$parse', function($parse) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + var model = $parse(attrs.fileModel); + var modelSetter = model.assign; + + element.bind('change', function() { + scope.$apply(function() { + modelSetter(scope, element[0].files[0]); + }); + }); + } + }; +} +]); diff --git a/src/main/resources/resource/WebGui/app/service/views/BoofCVGui.html b/src/main/resources/resource/WebGui/app/service/views/BoofCVGui.html new file mode 100644 index 0000000000..6431c8fd48 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/BoofCVGui.html @@ -0,0 +1,246 @@ + +

    +
    +
    + + + + + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + + +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    + +
    + + {{service.displayFilter}} mouse x y {{samplePoint.x}}x{{samplePoint.y}} {{stats.fps}} fps latency {{stats.latency}} ms + + +
    +
    + + + + + + + +
    + + + + +
    + +
    + + RECORDING + + +
    +
    +
    + + + + + + + + + + + +
    + + + +
    + + + +
    +
    + +
    + +
    + +  
    +
    + Adaptive threshold applies a threshold value based on a small region around a pixel.und it. + So we get different thresholds for different regions of the same image which gives better results for images with varying illumination. +
    + Block Size {{getFilter().blockSize}} + Subtracted {{getFilter().param1}} + +
    +
    + AddMask allows you to add a png with transparency as an overlay. This could be useful to mask off information for training. For example, removing a face portrait from its background. +
    + +
    +
    + In Euclidean geometry, an affine transformation is a geometric transformation that preserves lines and parallelism (but not necessarily distances and angles). + Rotational transformation would be an example. +
    + angle {{getFilter().angle}} +
    +
    +
    + Canny edge detection is a technique to extract useful structural information from different vision objects and dramatically reduce the amount of data to be processed. + It has been widely applied in various computer vision systems. +
    +
    + Aperture Size {{getFilter().apertureSize}} +
    + Low Threshold {{getFilter().lowThreshold}} +
    + High Threshold {{getFilter().highThreshold}} +
    +
    + Optical flow + is the pattern of apparent motion of image objects between two consecutive frames caused by the movemement of object or camera. + It is 2D vector field where each vector is a displacement vector showing the movement of points from first frame to second.. +
    +
    + Max Points {{getFilter().maxPointCnt}} +
    + Min Distance {{getFilter().minDistance}} +
    + Block Size {{getFilter().blockSize}} +
    + Quality {{getFilter().quality/100}} + + +
    + +
    +
    + From 545740d7e4a6f2aa093448af71dfd6df5c68922c Mon Sep 17 00:00:00 2001 From: grog Date: Sun, 13 Aug 2023 16:43:03 -0700 Subject: [PATCH 41/54] some removal of unused variables and debug stuff --- src/main/java/org/myrobotlab/io/Zip.java | 6 ++---- src/main/java/org/myrobotlab/service/Runtime.java | 4 ---- src/main/java/org/myrobotlab/service/WebGui.java | 14 +++++++++----- .../myrobotlab/service/config/ServiceConfig.java | 6 ------ .../service/interfaces/MockDocumentListener.java | 3 ++- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/myrobotlab/io/Zip.java b/src/main/java/org/myrobotlab/io/Zip.java index 56968ccc83..d41924ebda 100644 --- a/src/main/java/org/myrobotlab/io/Zip.java +++ b/src/main/java/org/myrobotlab/io/Zip.java @@ -299,28 +299,26 @@ static public void zipDirectory(File folder, String parentFolder, ZipOutputStrea } zos.putNextEntry(new ZipEntry(parentFolder + "/" + file.getName())); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); - long bytesRead = 0; byte[] bytesIn = new byte[BUFFER_SIZE]; int read = 0; while ((read = bis.read(bytesIn)) != -1) { zos.write(bytesIn, 0, read); - bytesRead += read; } zos.closeEntry(); + bis.close(); } } static public void zipFile(File file, ZipOutputStream zos) throws FileNotFoundException, IOException { zos.putNextEntry(new ZipEntry(file.getName())); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); - long bytesRead = 0; byte[] bytesIn = new byte[BUFFER_SIZE]; int read = 0; while ((read = bis.read(bytesIn)) != -1) { zos.write(bytesIn, 0, read); - bytesRead += read; } zos.closeEntry(); + bis.close(); } } \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 24682c4dfb..9c57690e44 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -441,10 +441,6 @@ synchronized private static Map createServicesFromPlan sc.state = "CREATED"; // process the base listeners/subscription of ServiceConfig si.addConfigListeners(sc); - if (si.getName().equals("mouth")) - { - log.info("here"); - } if (si instanceof ConfigurableService) { ((ConfigurableService)si).apply(sc); } diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index d5e0de4efd..dd2178bf8c 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -1178,6 +1178,14 @@ public static void main(String[] args) { try { // Platform.setVirtual(true); + + Runtime.startConfig("default"); + + boolean done = true; + if (done) { + return; + } + // Runtime.start("python", "Python"); // Arduino arduino = (Arduino)Runtime.start("arduino", "Arduino"); @@ -1192,11 +1200,7 @@ public static void main(String[] args) { // Runtime.start("intro", "Intro"); // Runtime.start("i01", "InMoov2"); - boolean done = true; - if (done) { - return; - } - + // Runtime.start("i01", "InMoov2"); // Runtime.start("python", "Python"); // Runtime.start("i01", "InMoov2"); diff --git a/src/main/java/org/myrobotlab/service/config/ServiceConfig.java b/src/main/java/org/myrobotlab/service/config/ServiceConfig.java index c85d43d947..59e8f9ff9a 100755 --- a/src/main/java/org/myrobotlab/service/config/ServiceConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ServiceConfig.java @@ -79,12 +79,6 @@ public String toString() { // heh non transient makes it easy to debug ! transient public String state = "INIT"; // INIT | LOADED | CREATED | STARTED | // STOPPED | RELEASED - // FIXME - SO IMPORTANT ! - - public String getx(String key) { - // FIXME - return reflected value - return null; - } public String getPath(String name, String peerKey) { if (name == null) { diff --git a/src/test/java/org/myrobotlab/service/interfaces/MockDocumentListener.java b/src/test/java/org/myrobotlab/service/interfaces/MockDocumentListener.java index d28b90bdd8..b00aa62fc0 100644 --- a/src/test/java/org/myrobotlab/service/interfaces/MockDocumentListener.java +++ b/src/test/java/org/myrobotlab/service/interfaces/MockDocumentListener.java @@ -5,8 +5,9 @@ import org.myrobotlab.document.Document; import org.myrobotlab.document.ProcessingStatus; import org.myrobotlab.framework.Service; +import org.myrobotlab.service.config.ServiceConfig; -public class MockDocumentListener extends Service implements DocumentListener { +public class MockDocumentListener extends Service implements DocumentListener { private static final long serialVersionUID = 1L; private int count = 0; From 4a01e1b794736e141969e783bdd4aa23ae4d1635 Mon Sep 17 00:00:00 2001 From: grog Date: Sun, 13 Aug 2023 16:53:24 -0700 Subject: [PATCH 42/54] trying to be consistent with names --- .../java/org/myrobotlab/boofcv/BoofCVFilter.java | 4 ++-- .../org/myrobotlab/cv/{CvData.java => CVData.java} | 2 +- .../myrobotlab/cv/{CvFilter.java => CVFilter.java} | 2 +- .../java/org/myrobotlab/cv/ComputerVision.java | 2 +- .../java/org/myrobotlab/opencv/OpenCVData.java | 10 +++++----- .../java/org/myrobotlab/opencv/OpenCVFilter.java | 4 ++-- src/main/java/org/myrobotlab/service/BoofCV.java | 4 ++-- .../java/org/myrobotlab/service/JMonkeyEngine.java | 4 ++-- src/main/java/org/myrobotlab/service/OpenCV.java | 14 +++++++++----- 9 files changed, 25 insertions(+), 21 deletions(-) rename src/main/java/org/myrobotlab/cv/{CvData.java => CVData.java} (88%) rename src/main/java/org/myrobotlab/cv/{CvFilter.java => CVFilter.java} (80%) diff --git a/src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java b/src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java index 3e4f09b5a5..c8d5ff5736 100644 --- a/src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java +++ b/src/main/java/org/myrobotlab/boofcv/BoofCVFilter.java @@ -1,11 +1,11 @@ package org.myrobotlab.boofcv; -import org.myrobotlab.cv.CvFilter; +import org.myrobotlab.cv.CVFilter; import org.myrobotlab.service.BoofCV; import boofcv.struct.image.ImageBase; -public abstract class BoofCVFilter implements CvFilter { +public abstract class BoofCVFilter implements CVFilter { protected String name; public String getName() { diff --git a/src/main/java/org/myrobotlab/cv/CvData.java b/src/main/java/org/myrobotlab/cv/CVData.java similarity index 88% rename from src/main/java/org/myrobotlab/cv/CvData.java rename to src/main/java/org/myrobotlab/cv/CVData.java index 67e75ae479..3db4d9ad96 100644 --- a/src/main/java/org/myrobotlab/cv/CvData.java +++ b/src/main/java/org/myrobotlab/cv/CVData.java @@ -13,7 +13,7 @@ * @author GroG * */ -public abstract class CvData implements Serializable { +public abstract class CVData implements Serializable { private static final long serialVersionUID = 1L; public static final String POINT_CLOUDS = "point.cloud"; diff --git a/src/main/java/org/myrobotlab/cv/CvFilter.java b/src/main/java/org/myrobotlab/cv/CVFilter.java similarity index 80% rename from src/main/java/org/myrobotlab/cv/CvFilter.java rename to src/main/java/org/myrobotlab/cv/CVFilter.java index cb144d4091..5d68375789 100644 --- a/src/main/java/org/myrobotlab/cv/CvFilter.java +++ b/src/main/java/org/myrobotlab/cv/CVFilter.java @@ -1,6 +1,6 @@ package org.myrobotlab.cv; -public interface CvFilter { +public interface CVFilter { public void enable(); diff --git a/src/main/java/org/myrobotlab/cv/ComputerVision.java b/src/main/java/org/myrobotlab/cv/ComputerVision.java index 56ba55e2b5..b6bf9bbc2e 100644 --- a/src/main/java/org/myrobotlab/cv/ComputerVision.java +++ b/src/main/java/org/myrobotlab/cv/ComputerVision.java @@ -13,7 +13,7 @@ public interface ComputerVision extends NameProvider { void capture(); - CvFilter addFilter(String name, String filterType); + CVFilter addFilter(String name, String filterType); void removeFilter(String name); diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVData.java b/src/main/java/org/myrobotlab/opencv/OpenCVData.java index a581a77a9b..3449ec6350 100644 --- a/src/main/java/org/myrobotlab/opencv/OpenCVData.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVData.java @@ -27,7 +27,7 @@ import org.bytedeco.javacv.FrameGrabber; import org.bytedeco.opencv.opencv_core.IplImage; import org.bytedeco.opencv.opencv_core.Mat; -import org.myrobotlab.cv.CvData; +import org.myrobotlab.cv.CVData; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.geometry.Point2df; @@ -81,7 +81,7 @@ * @author GroG * */ -public class OpenCVData extends CvData { +public class OpenCVData extends CVData { public final static Logger log = LoggerFactory.getLogger(OpenCVData.class); private static final long serialVersionUID = 1L; @@ -542,7 +542,7 @@ public void writeAll() { @Override public List getPointCloudList() { - return (List) sources.get(CvData.POINT_CLOUDS); + return (List) sources.get(CVData.POINT_CLOUDS); } @Override @@ -560,12 +560,12 @@ public Set getKeySet() { } public void put(PointCloud pc) { - List pcs = (List) sources.get(CvData.POINT_CLOUDS); + List pcs = (List) sources.get(CVData.POINT_CLOUDS); if (pcs == null) { pcs = new ArrayList(); } pcs.add(pc); - sources.put(CvData.POINT_CLOUDS, pcs); + sources.put(CVData.POINT_CLOUDS, pcs); } public ArrayList getDetectedText() { diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java index b43895d4eb..ac90bae143 100644 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java @@ -46,7 +46,7 @@ import org.bytedeco.javacv.CanvasFrame; import org.bytedeco.opencv.opencv_core.IplImage; import org.bytedeco.opencv.opencv_core.Mat; -import org.myrobotlab.cv.CvFilter; +import org.myrobotlab.cv.CVFilter; import org.myrobotlab.document.Classification; import org.myrobotlab.framework.Service; import org.myrobotlab.logging.LoggerFactory; @@ -54,7 +54,7 @@ import org.myrobotlab.service.OpenCV; import org.slf4j.Logger; -public abstract class OpenCVFilter implements Serializable, CvFilter { +public abstract class OpenCVFilter implements Serializable, CVFilter { public final static Logger log = LoggerFactory.getLogger(OpenCVFilter.class.toString()); private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/BoofCV.java b/src/main/java/org/myrobotlab/service/BoofCV.java index b108b129e0..3da1e0a523 100644 --- a/src/main/java/org/myrobotlab/service/BoofCV.java +++ b/src/main/java/org/myrobotlab/service/BoofCV.java @@ -10,7 +10,7 @@ import org.myrobotlab.boofcv.BoofCVFilter; import org.myrobotlab.boofcv.BoofCVFilterTrackerObjectQuad; import org.myrobotlab.cv.ComputerVision; -import org.myrobotlab.cv.CvFilter; +import org.myrobotlab.cv.CVFilter; import org.myrobotlab.framework.Instantiator; import org.myrobotlab.framework.Service; import org.myrobotlab.image.WebImage; @@ -144,7 +144,7 @@ public BoofCV(String n, String id) { } @Override - public CvFilter addFilter(String name, String filterType) { + public CVFilter addFilter(String name, String filterType) { String type = String.format("org.myrobotlab.boofcv.BoofCVFilter%s", filterType); BoofCVFilter filter = (BoofCVFilter) Instantiator.getNewInstance(type, name); if (filter == null) { diff --git a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java index 5f932f8853..fbc5ef5e99 100644 --- a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java +++ b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java @@ -24,7 +24,7 @@ import java.util.concurrent.Future; import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.cv.CvData; +import org.myrobotlab.cv.CVData; import org.myrobotlab.framework.Instantiator; import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Platform; @@ -1522,7 +1522,7 @@ public void onAnalog(String name, float keyPressed, float tpf) { * @param data * cv data */ - public void onCvData(CvData data) { + public void onCvData(CVData data) { // onPointCloud(data.getPointCloud()); FIXME - brittle and not correct // FIXME - do something interesting ... :) } diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index 7a26a17dca..7abc45de8b 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -95,8 +95,8 @@ import org.bytedeco.opencv.opencv_core.Rect; import org.bytedeco.opencv.opencv_imgproc.CvFont; import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.cv.CvData; -import org.myrobotlab.cv.CvFilter; +import org.myrobotlab.cv.CVData; +import org.myrobotlab.cv.CVFilter; import org.myrobotlab.document.Classification; import org.myrobotlab.document.Classifications; import org.myrobotlab.framework.Instantiator; @@ -651,15 +651,19 @@ synchronized public OpenCVFilter addFilter(OpenCVFilter filter) { * - name of filter * @return the filter */ - public CvFilter addFilter(String filterName) { + public CVFilter addFilter(String filterName) { String filterType = filterName.substring(0, 1).toUpperCase() + filterName.substring(1); return addFilter(filterName, filterType); } @Override - public CvFilter addFilter(String name, String filterType) { + public CVFilter addFilter(String name, String filterType) { String type = String.format("org.myrobotlab.opencv.OpenCVFilter%s", filterType); OpenCVFilter filter = (OpenCVFilter) Instantiator.getNewInstance(type, name); + if (filter == null) { + error("cannot create filter %s of type %s", name, type); + return null; + } addFilter(filter); return filter; } @@ -1439,7 +1443,7 @@ public final OpenCVData publishOpenCVData(OpenCVData data) { return data; } - public final CvData publishCvData(CvData data) { + public final CVData publishCvData(CVData data) { return data; } From 3bd69b1949f72cf6bb4a33e34233146e7b2159b0 Mon Sep 17 00:00:00 2001 From: grog Date: Sun, 13 Aug 2023 17:04:42 -0700 Subject: [PATCH 43/54] a little ui cleanup --- src/main/resources/resource/WebGui/app/mrl.js | 43 +++++++++---------- .../WebGui/app/service/js/IntroGui.js | 5 +++ .../WebGui/app/service/views/ArduinoGui.html | 6 +-- .../WebGui/app/service/views/Mpu6050Gui.html | 4 +- .../WebGui/app/service/views/OpenCVGui.html | 7 +-- .../resource/WebGui/app/widget/oscope.js | 3 -- 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/main/resources/resource/WebGui/app/mrl.js b/src/main/resources/resource/WebGui/app/mrl.js index 78ed75225b..81cfef0b0e 100644 --- a/src/main/resources/resource/WebGui/app/mrl.js +++ b/src/main/resources/resource/WebGui/app/mrl.js @@ -1465,41 +1465,40 @@ angular.module('mrlapp.mrl', []).provider('mrl', [function() { return arrayOfServices }, - controllerscope: _self.controllerscope, - setSearchFunction: _self.setSearchFunction, - setNavCtrl: _self.setNavCtrl, - setTabsViewCtrl: _self.setTabsViewCtrl, - error: _self.error, changeTab: _self.changeTab, - goBack: _self.goBack, - search: _self.search, + controllerscope: _self.controllerscope, createMessage: _self.createMessage, display: _self.display, + error: _self.error, + getDisplayName: _self.getDisplayName, getDisplayImages: getDisplayImages, - setDisplayCallback: setDisplayCallback, - subscribeToUpdates: _self.subscribeToUpdates, - subscribeToRegistered: _self.subscribeToRegistered, - subscribeToReleased: _self.subscribeToReleased, - getPanelList: _self.getPanelList, + getFullName: _self.getFullName, getPanel: _self.getPanel, - sendTo: _self.sendTo, + getPanelList: _self.getPanelList, + getProperties: _self.getProperties, getShortName: _self.getShortName, getSimpleName: _self.getSimpleName, getStyle: _self.getStyle, - subscribe: _self.subscribe, - unsubscribe: _self.unsubscribe, + goBack: _self.goBack, isPeerStarted: _self.isPeerStarted, - subscribeToService: _self.subscribeToService, - getFullName: _self.getFullName, + search: _self.search, + sendMessage: _self.sendMessage, sendBlockingMessage: _self.sendBlockingMessage, + sendTo: _self.sendTo, + setNavCtrl: _self.setNavCtrl, + setDisplayCallback: setDisplayCallback, + setSearchFunction: _self.setSearchFunction, + setTabsViewCtrl: _self.setTabsViewCtrl, + subscribe: _self.subscribe, subscribeConnected: _self.subscribeConnected, + subscribeTo: _self.subscribeTo, subscribeToMethod: _self.subscribeToMethod, + subscribeToReleased: _self.subscribeToReleased, + subscribeToRegistered: _self.subscribeToRegistered, + subscribeToService: _self.subscribeToService, subscribeToServiceMethod: _self.subscribeToServiceMethod, - subscribeTo: _self.subscribeTo, - // better name - getProperties: _self.getProperties, - sendMessage: _self.sendMessage, - // setViewType: _self.setViewType, + subscribeToUpdates: _self.subscribeToUpdates, + unsubscribe: _self.unsubscribe, interfaceToPossibleServices: _self.interfaceToPossibleServices } diff --git a/src/main/resources/resource/WebGui/app/service/js/IntroGui.js b/src/main/resources/resource/WebGui/app/service/js/IntroGui.js index 81721cc219..ae72006958 100644 --- a/src/main/resources/resource/WebGui/app/service/js/IntroGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/IntroGui.js @@ -64,6 +64,11 @@ angular.module('mrlapp.service.IntroGui', []).controller('IntroGuiCtrl', ['$scop return ret } + $scope.start = function(name, type) { + msg.sendTo('runtime', 'start', name, type) + } + + // this method initializes subPanels when a new service becomes available this.onRegistered = function(panel) { if (panelNames.has(panel.displayName)) { diff --git a/src/main/resources/resource/WebGui/app/service/views/ArduinoGui.html b/src/main/resources/resource/WebGui/app/service/views/ArduinoGui.html index 056eafa938..1bce530544 100644 --- a/src/main/resources/resource/WebGui/app/service/views/ArduinoGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/ArduinoGui.html @@ -26,9 +26,6 @@ -
    - -
    {{service.boardType}} {{connectedStatus}} {{versionStatus}}
    version {{boardInfo.version}} @@ -72,6 +69,9 @@ + + +
    diff --git a/src/main/resources/resource/WebGui/app/service/views/Mpu6050Gui.html b/src/main/resources/resource/WebGui/app/service/views/Mpu6050Gui.html index 9d4d5453b4..fab190b13f 100644 --- a/src/main/resources/resource/WebGui/app/service/views/Mpu6050Gui.html +++ b/src/main/resources/resource/WebGui/app/service/views/Mpu6050Gui.html @@ -14,8 +14,8 @@ - - + +
    diff --git a/src/main/resources/resource/WebGui/app/service/views/OpenCVGui.html b/src/main/resources/resource/WebGui/app/service/views/OpenCVGui.html index 3cf2ec2821..6431c8fd48 100644 --- a/src/main/resources/resource/WebGui/app/service/views/OpenCVGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/OpenCVGui.html @@ -226,9 +226,8 @@
    - diff --git a/src/main/resources/resource/WebGui/app/widget/oscope.js b/src/main/resources/resource/WebGui/app/widget/oscope.js index 11cc94c2c0..d45a9f9a20 100644 --- a/src/main/resources/resource/WebGui/app/widget/oscope.js +++ b/src/main/resources/resource/WebGui/app/widget/oscope.js @@ -201,7 +201,6 @@ angular.module('mrlapp.service').directive('oscope', ['mrl', function(mrl) { _self.ctx.fillText(pinDef.name + (' AVG ' + (stats.totalValue / stats.totalSample)).substring(0, 11) + ' MIN ' + stats.min + ' MAX ' + stats.max, 10, 18) } } - // RENAME eanbleTrace - FIXME read values vs write values | ALL values from service not from ui !! - ui only sends commands scope.activateTrace = function(pinDef) { var trace = scope.oscope.traces[pinDef.pin] @@ -253,7 +252,6 @@ angular.module('mrlapp.service').directive('oscope', ['mrl', function(mrl) { } } } - scope.toggleWriteButton = function(pinDef) { var highlight = trace.color.getOriginalInput() if (trace.state) { @@ -274,7 +272,6 @@ angular.module('mrlapp.service').directive('oscope', ['mrl', function(mrl) { } } } - // FIXME FIXME FIXME ->> THIS SHOULD WORK subscribeToServiceMethod <- but doesnt mrl.subscribeToService(_self.onMsg, name) // this siphons off a single subscribe to the webgui From ac2216adf16baef2e5215c7f64eace4428df71bc Mon Sep 17 00:00:00 2001 From: grog Date: Sun, 13 Aug 2023 17:05:36 -0700 Subject: [PATCH 44/54] yml updates --- src/main/resources/resource/Gpt3/gpt3.yml | 17 +++++++++++++++++ src/main/resources/resource/InMoov2/inmoov2.yml | 1 + 2 files changed, 18 insertions(+) create mode 100644 src/main/resources/resource/Gpt3/gpt3.yml create mode 100644 src/main/resources/resource/InMoov2/inmoov2.yml diff --git a/src/main/resources/resource/Gpt3/gpt3.yml b/src/main/resources/resource/Gpt3/gpt3.yml new file mode 100644 index 0000000000..e716fbde16 --- /dev/null +++ b/src/main/resources/resource/Gpt3/gpt3.yml @@ -0,0 +1,17 @@ +!!org.myrobotlab.service.config.Gpt3Config +currentUserName: null +engine: gpt-3.5-turbo +listeners: null +maxTokens: 256 +peers: + http: + autoStart: true + name: gpt3.http + type: HttpClient +sleepWord: sleep +sleeping: false +temperature: 0.7 +token: null +type: Gpt3 +url: https://api.openai.com/v1/chat/completions +wakeWord: wake diff --git a/src/main/resources/resource/InMoov2/inmoov2.yml b/src/main/resources/resource/InMoov2/inmoov2.yml new file mode 100644 index 0000000000..ce01362503 --- /dev/null +++ b/src/main/resources/resource/InMoov2/inmoov2.yml @@ -0,0 +1 @@ +hello From 6c5988cadc7c82ca3de3777a90e764d4326db6aa Mon Sep 17 00:00:00 2001 From: grog Date: Sun, 13 Aug 2023 17:07:17 -0700 Subject: [PATCH 45/54] ivywrapper logical and or not bitwise --- src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java b/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java index 5e3d4d21d0..023411b3f8 100644 --- a/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java +++ b/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java @@ -137,7 +137,7 @@ public void createIvyForDependency(String location, ServiceDependency dependency dependency.getVersion() == null ? "latest.integration" : dependency.getVersion())); List excludes = dependency.getExcludes(); - boolean twoTags = dependency.getExt() != null || excludes != null & excludes.size() > 0; + boolean twoTags = dependency.getExt() != null || excludes != null && excludes.size() > 0; if (twoTags) { // more stuffs ! - we have 2 tags - end this one without /> sb.append(">\n"); @@ -152,7 +152,7 @@ public void createIvyForDependency(String location, ServiceDependency dependency } // exclusions begin --- - if (excludes != null & excludes.size() > 0) { + if (excludes != null && excludes.size() > 0) { StringBuilder ex = new StringBuilder(); for (ServiceExclude exclude : excludes) { ex.append(" Date: Sun, 13 Aug 2023 17:12:08 -0700 Subject: [PATCH 46/54] test updates --- .../java/org/myrobotlab/framework/MethodCacheTest.java | 9 --------- .../java/org/myrobotlab/service/ArduinoChaosTest.java | 5 ++--- src/test/java/org/myrobotlab/service/ArduinoTest.java | 6 +++++- src/test/java/org/myrobotlab/service/HarryTest.java | 2 +- src/test/resources/InMoov/Harry.py | 4 +++- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/myrobotlab/framework/MethodCacheTest.java b/src/test/java/org/myrobotlab/framework/MethodCacheTest.java index 4bc69efd87..b92cc872c7 100644 --- a/src/test/java/org/myrobotlab/framework/MethodCacheTest.java +++ b/src/test/java/org/myrobotlab/framework/MethodCacheTest.java @@ -6,7 +6,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; import org.junit.BeforeClass; import org.junit.Test; @@ -14,7 +13,6 @@ import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; -import org.myrobotlab.service.Clock; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.TestCatcher; import org.myrobotlab.service.TestCatcher.Ball; @@ -32,15 +30,8 @@ public class MethodCacheTest extends AbstractTest { @BeforeClass public static void setUpBeforeClass() throws Exception { cache = MethodCache.getInstance(); - sleep(100); cache.clear(); assertEquals("all clear should be 0", 0, cache.getObjectSize()); - // cache 3 services method entries - cache.cacheMethodEntries(Runtime.class); - cache.cacheMethodEntries(TestCatcher.class); - cache.cacheMethodEntries(Clock.class); - assertEquals(String.format("cached 3 object's methods %s", Arrays.toString(cache.getCachedObjectNames().toArray())), 3, cache.getObjectSize()); - tester = (TestCatcher) Runtime.start("tester", "TestCatcher"); } diff --git a/src/test/java/org/myrobotlab/service/ArduinoChaosTest.java b/src/test/java/org/myrobotlab/service/ArduinoChaosTest.java index 32089148d8..2592ab4165 100644 --- a/src/test/java/org/myrobotlab/service/ArduinoChaosTest.java +++ b/src/test/java/org/myrobotlab/service/ArduinoChaosTest.java @@ -9,7 +9,6 @@ import org.myrobotlab.arduino.ArduinoUtils; import org.myrobotlab.framework.MrlException; -import com.pi4j.jni.Serial; @Ignore public class ArduinoChaosTest { @@ -123,9 +122,9 @@ public void testArduino() throws IOException, MrlException, InterruptedException // arduino.setDigitalTriggerOnly(false); Thread.sleep(1000); - arduino.setSerialRate(Serial.BAUD_RATE_57600); + arduino.setSerialRate(Serial.BAUD_115200); Thread.sleep(1000); - arduino.setSerialRate(Serial.BAUD_RATE_115200); + arduino.setSerialRate(Serial.BAUD_115200); Thread.sleep(1000); arduino.getBoardInfo(); diff --git a/src/test/java/org/myrobotlab/service/ArduinoTest.java b/src/test/java/org/myrobotlab/service/ArduinoTest.java index 9add9d8fc4..5dff17f399 100644 --- a/src/test/java/org/myrobotlab/service/ArduinoTest.java +++ b/src/test/java/org/myrobotlab/service/ArduinoTest.java @@ -213,8 +213,12 @@ public final void pinArrayTest() { arduino01.enablePin(10); arduino01.enablePin(12); arduino01.enablePin(13); - sleep(100); + + // Wait for pin enablement to complete + sleep(200); arduino01.reset(); + // Wait for reset to complete + sleep(100); assertTrue("did not get pin array data D10", catcher.containsPinArrayFromPin(arduino01.getPin(10).getPinName())); assertTrue("did not get pin array data D12", catcher.containsPinArrayFromPin(arduino01.getPin(12).getPinName())); diff --git a/src/test/java/org/myrobotlab/service/HarryTest.java b/src/test/java/org/myrobotlab/service/HarryTest.java index d08da9ccbd..29527a0845 100755 --- a/src/test/java/org/myrobotlab/service/HarryTest.java +++ b/src/test/java/org/myrobotlab/service/HarryTest.java @@ -224,7 +224,7 @@ public void testHarry() throws Exception { InMoov2 i01 = (InMoov2) Runtime.createAndStart("i01", "InMoov2"); i01.setMute(true); - i01.startAll(); + // i01.startAll(); // if startInMoov: // i01.startAll(leftPort, rightPort) // else: diff --git a/src/test/resources/InMoov/Harry.py b/src/test/resources/InMoov/Harry.py index 000c76c864..091cca224f 100644 --- a/src/test/resources/InMoov/Harry.py +++ b/src/test/resources/InMoov/Harry.py @@ -73,7 +73,9 @@ def heard(data): i01 = runtime.start("i01", "InMoov2") i01.setMute(True) if startInMoov: - i01.startAll(leftPort, rightPort) + # use config to start + # i01.startAll(leftPort, rightPort) + pass else: i01.mouth = mouth From aae0121c7a2fb80edb5601413de4cfd6f140a9ae Mon Sep 17 00:00:00 2001 From: Langevin Gael Date: Fri, 18 Aug 2023 15:55:22 +0200 Subject: [PATCH 47/54] Update AudioFileGui.html (#1334) * Update AudioFileGui.html * Update AudioFileGui.js * Update AudioFileGui.js --- .../resource/WebGui/app/service/js/AudioFileGui.js | 10 ++++++++++ .../WebGui/app/service/views/AudioFileGui.html | 12 ++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js b/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js index ef97b14ff0..1edc44bbf8 100644 --- a/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js @@ -34,6 +34,16 @@ angular.module('mrlapp.service.AudioFileGui', []).controller('AudioFileGuiCtrl', } } + $scope.stopPlaylist = function() { + if ($scope.selectedPlaylist) { + msg.send('stopPlaylist', $scope.selectedPlaylist[0]) + msg.send('stop') + } else { + msg.send('stopPlaylist') + msg.send('stop') + } + } + // GOOD TEMPLATE TO FOLLOW this.updateState = function(service) { $scope.service = service diff --git a/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html b/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html index ef99c2a8fa..93e1cac98b 100644 --- a/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html @@ -11,17 +11,17 @@ +