smartplant 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,28 @@
1
1
 
2
- # SmartPlant
2
+ # SmartPlant by *PIGEONPOSSE*
3
3
 
4
4
  [![HEADER](https://github.com/pigeonposse/.github/blob/main/docs/banner-smartplant.png?raw=true)](https://github.com/pigeonposse)
5
5
 
6
6
 
7
- **SmartPlant** is a pioneering library designed for plant care with AI technology, aimed at integrating automated alerts with multi-language support. This standard prototype library is crafted for intelligent devices that manage plant care, enhancing the level of interaction and intelligence between plants and their owners. The core idea behind SmartPlant is to pave the way for advancements in plant care technology. It serves as a foundation for developing more sophisticated solutions and experimenting with innovative devices that cater to the needs of plants. By leveraging this library, developers can contribute to evolving the ecosystem of smart plant care.
7
+ **SmartPlant** is a library designed to simplify plant care through *the integration of advanced artificial intelligence models*. This technology not only researches detailed information about each type of plant but also determines the optimal conditions for their care, thereby maximizing their growth and health. Thanks to this functionality, users can efficiently monitor and manage the environment of their plants *using sensors that measure humidity, light, and temperature*.
8
+
9
+ The core idea behind SmartPlant is to *pave the way for advancements in plant care technology*. It serves as a foundation for developing more sophisticated solutions and experimenting with innovative devices that meet the needs of plants. By leveraging this library, developers can contribute to the evolution of the smart plant care ecosystem.
10
+
11
+ > [!WARNING]
12
+ > Currently in phase `Beta`
13
+
14
+ ## AI and the future intertwine: connect with your environment.
8
15
 
9
16
  ## Features
10
17
 
11
- * Multi-language support
12
- * Alerts for plant care (moisture, sunlight, water, soil, temperature)
13
- * Local and external input methods
14
- * Automatic configuration and notifications
18
+ - 🤖 **Integrated Artificial Intelligence:** Optimizes the care of each plant.
19
+ - 📊 **Real-Time Monitoring:** Collects humidity, light, and temperature with sensors.
20
+ - 🔔 **Customized Alerts:** Notifications for out-of-range conditions.
21
+ - 💬 **Multilingual Support:** English, Español, Français, Deutsch, Italiano, Português, Nederlands, Русский, 中文, 日本語.
22
+ - 🎛 **Easy Setup:** Intuitive process for Raspberry Pi or Arduino.
23
+ - 📈 **Data History:** Environmental trend analysis.
24
+ - 🔮 **Critical Condition Prediction:** Prevents significant changes in plant health.
25
+ - 🌍 **Open-source:** MIT licensed, available for public use and contributions.
15
26
 
16
27
  ## Installation
17
28
 
@@ -21,98 +32,77 @@ To install the library, use npm:
21
32
  npm install smartplant
22
33
  ```
23
34
 
24
- ## Usage
25
-
26
- To start using SmartPlant, follow these steps:
27
-
28
- 1. **Import the Library**
29
-
30
- ```
31
- import SmartPlant from 'smartplant'
32
- ```
33
-
34
- 2. **Create an Instance and Configure**
35
-
36
- ```
37
-
38
- const smartPlant = new SmartPlant();
39
-
40
- // Select language
41
- smartPlant.setLanguage('en'); // Options: 'en', 'es', 'fr', 'de', 'it', 'pt', 'nl', 'ru', 'zh', 'ja'
42
-
43
- // Select input method
44
- smartPlant.setInputMethod('local'); // Options: 'local', 'extern'
45
-
46
- ```
47
-
48
- 3. **Configure the Plant**
49
-
50
- ```
51
-
52
- smartPlant.setPlantType('indoor'); // Options: 'indoor', 'outdoor'
53
- smartPlant.setPlantName('Ficus'); // Replace 'Ficus' with the name of your plant
54
-
55
- // Configure alerts
56
- smartPlant.configureAlerts();
57
-
58
- ```
59
-
60
- 4. **Start Monitoring**
61
-
62
- ```
63
- smartPlant.startMonitoring();
64
- ```
65
-
66
-
67
- ## Language Configuration
68
-
69
- You can configure the language as follows:
35
+ # Benefits of the emojis system
70
36
 
71
- ```
72
- smartPlant.setLanguage('en'); // Change 'en' to the desired language code
73
- ```
37
+ This approach with emojis provides a simplified and visually attractive user experience.It allows even those without deep technical knowledge to quickly understand the state of the plant and take the necessary measures.It is a way to offer immediate and clear feedback, improving interaction with the system and promoting more regular and careful maintenance of the plant.
38
+
39
+ ## Emojis scales for parameter monitoring
40
+
41
+ To make the plant monitoring more intuitive and visually accessible, we have implemented an emojis system that represents different levels of each critical parameter: moisture, light and temperature.Each emoji offers a rapid representation of the current status of the parameter, which facilitates interpretation without analyzing specific numbers.
42
+
43
+ #### Humidity:
44
+ - 🍂 **Very dry:** Indicates that moisture is below the recommended minimum range.The plant is at risk of dehydration.
45
+ - 🌿 **Ideal:** Moisture is within the ideal range, which means that the plant is in optimal conditions.
46
+ - 💧 **Slightly wet:** Indicates that moisture is slightly above the ideal range, but it is not yet worrisome.
47
+ - 🌊 **Very humid:** Moisture is above the maximum allowed range, which could lead to saturation and problems such as waterlogging.
48
+
49
+ #### Light:
50
+ - 🌑 **Very little light:** points out that the plant receives less light than necessary, which could affect its growth.
51
+ - 🌥 **Ideal:** The plant receives the amount of light adequate for healthy development.
52
+ - 🌞 **Too much light:** Light exposure is excessive, which can cause burns or stress in the plant.
53
+
54
+ #### Temperature:
55
+ - 🧊 **Very cold:** The temperature is below the minimum range, which can slow down or damage the plant.
56
+ - 🌡️ **Ideal:** The temperature is in the optimal range for the growth and development of the plant.
57
+ - 🔥 **Very hot:** The temperature exceeds the maximum range, which could cause overheating and dehydration.
74
58
 
75
- Available languages are:
59
+ ### Emojis-based happiness system
76
60
 
77
- * en - English
78
- * es - Spanish
79
- * fr - French
80
- * de - German
81
- * it - Italian
82
- * pt - Portuguese
83
- * nl - Dutch
84
- * ru - Russian
85
- * zh - Chinese
86
- * ja - Japanese
61
+ In addition to the specific parameters, we have designed a system of general happiness for the plant, which is represented with caritas emojis.This system provides a global vision of the state of the plant, based on a combination of its levels of humidity, light and temperature.
87
62
 
88
- ## Input Methods
63
+ #### Happiness scale:
64
+ - 🤩 **Very happy:** The plant is in ideal conditions in all key parameters.This is the optimal state.
65
+ - 😊 **Happy:** The plant is in good condition, although there could be slight deviations in some parameters.
66
+ - 😐 **Acceptable:** The plant is in acceptable conditions, but is far from ideal.Small adjustments may be required.
67
+ - 😞 **Bad conditions:** The plant is experiencing unfavorable conditions and needs attention to avoid major damage.
68
+ - 😖 **Critical conditions:** The plant is in a critical state and requires immediate action to prevent its condition will get worse.
69
+ - 🥵 **Extremely critical:** The plant is in an extremely critical state and is in danger of dying if urgent measures are not taken.
70
+ - 😵 **No Data Detected:** Vital signals missing or sensors not transmitting requiring immediate check.
71
+
72
+ ## ☕ Donate
89
73
 
90
- Choose between:
74
+ Help us to develop more interesting things.
91
75
 
92
- * **Local**: Detects plants on your computer.
93
- * **External**: Uses an external API; requires an API key.
76
+ [![Donate](https://img.shields.io/badge/Donate-grey?style=for-the-badge)](https://pigeonposse.com/?popup=donate)
94
77
 
95
- ## Alert Messages
78
+ ## 📜 License
96
79
 
97
- \*\*SmartPlant\*\* provides customized messages based on the plant's needs. Here are some examples:
80
+ This software is licensed with **[GPL-3.0](/LICENSE)**.
98
81
 
99
- * **Low Moisture**: "The moisture in my soil is low. I need water!"
100
- * **Low Sunlight**: "I am receiving too little sunlight. I need more sun!"
101
- * **High Temperature**: "The temperature is too high. Please lower it!"
102
- * **Happy**: "I am very happy and healthy!"
82
+ [![Read more](https://img.shields.io/badge/Read-more-grey?style=for-the-badge)](/LICENSE)
103
83
 
104
- ## Contributions
84
+ ## 🐦 About us
105
85
 
106
- Contributions are welcome. If you wish to contribute, please review the \[CONTRIBUTING.md\](CONTRIBUTING.md) file for more details.
86
+ *PigeonPosse* is a **code development collective** focused on creating practical and interesting tools that help developers and users enjoy a more agile and comfortable experience. Our projects cover various programming sectors and we do not have a thematic limitation in terms of projects.
107
87
 
108
- ## License
88
+ [![More](https://img.shields.io/badge/Read-more-grey?style=for-the-badge)](https://github.com/pigeonposse)
109
89
 
110
- This project is licensed under the MIT License - see the \[LICENSE\](LICENSE) file for details.
90
+ ### Collaborators
111
91
 
112
- ## Contact
92
+ | | Name | Role | GitHub |
93
+ | ---------------------------------------------------------------------------------- | ----------- | ------------ | ---------------------------------------------- |
94
+ | <img src="https://github.com/alejomalia.png?size=72" alt="Angelo" style="border-radius:100%"/> | Alejo | Author & Development | [@alejomalia](https://github.com/alejomalia) |
95
+ | <img src="https://github.com/PigeonPosse.png?size=72" alt="PigeonPosse" style="border-radius:100%"/> | PigeonPosse | Collective | [@PigeonPosse](https://github.com/PigeonPosse) |
113
96
 
114
- For questions or support, contact \[your-email@example.com\](mailto:your-email@example.com).
97
+ <br>
98
+ <p align="center">
115
99
 
116
- - - -
100
+ [![Web](https://img.shields.io/badge/Web-grey?style=for-the-badge&logoColor=white)](https://pigeonposse.com)
101
+ [![About Us](https://img.shields.io/badge/About%20Us-grey?style=for-the-badge&logoColor=white)](https://pigeonposse.com?popup=about)
102
+ [![Donate](https://img.shields.io/badge/Donate-pink?style=for-the-badge&logoColor=white)](https://pigeonposse.com/?popup=donate)
103
+ [![Github](https://img.shields.io/badge/Github-black?style=for-the-badge&logo=github&logoColor=white)](https://github.com/pigeonposse)
104
+ [![Twitter](https://img.shields.io/badge/Twitter-black?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/pigeonposse_)
105
+ [![Instagram](https://img.shields.io/badge/Instagram-black?style=for-the-badge&logo=instagram&logoColor=white)](https://www.instagram.com/pigeon.posse/)
106
+ [![Medium](https://img.shields.io/badge/Medium-black?style=for-the-badge&logo=medium&logoColor=white)](https://medium.com/@pigeonposse)
117
107
 
118
- Thank you for using **SmartPlant**. We hope you enjoy managing your plants with our library.
108
+ </p>
package/main.js ADDED
@@ -0,0 +1,606 @@
1
+ import { execSync } from 'child_process';
2
+ import inquirer from 'inquirer';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { SerialPort } from 'serialport';
6
+ import { ReadlineParser } from '@serialport/parser-readline';
7
+ import chalk from 'chalk';
8
+ import { fileURLToPath } from 'url';
9
+ import axios from 'axios';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ function loadMessages(language) {
15
+ const languageFile = path.join(__dirname, `language/messages-${language}.json`);
16
+ try {
17
+ return JSON.parse(fs.readFileSync(languageFile, 'utf8'));
18
+ } catch (error) {
19
+ console.error(`Error loading language file: ${error.message}`);
20
+ return JSON.parse(fs.readFileSync(path.join(__dirname, 'language/messages-en.json'), 'utf8'));
21
+ }
22
+ }
23
+
24
+ class AIClient {
25
+ constructor(type, apiKey, localModel) {
26
+ this.type = type;
27
+ this.apiKey = apiKey;
28
+ this.localModel = localModel;
29
+ }
30
+
31
+ async generateResponse(prompt, language) {
32
+ const languagePrompt = `Respond in ${language}. `;
33
+ const fullPrompt = languagePrompt + prompt;
34
+
35
+ switch (this.type) {
36
+ case 'openai':
37
+ return this.generateOpenAIResponse(fullPrompt);
38
+ case 'local':
39
+ return this.generateLocalResponse(fullPrompt);
40
+ default:
41
+ throw new Error('Unsupported AI type');
42
+ }
43
+ }
44
+
45
+ async generateOpenAIResponse(prompt) {
46
+ try {
47
+ const response = await axios.post('https://api.openai.com/v1/engines/davinci-codex/completions', {
48
+ prompt: prompt,
49
+ max_tokens: 500,
50
+ n: 1,
51
+ stop: null,
52
+ temperature: 0.7,
53
+ }, {
54
+ headers: {
55
+ 'Authorization': `Bearer ${this.apiKey}`,
56
+ 'Content-Type': 'application/json'
57
+ }
58
+ });
59
+ return response.data.choices[0].text.trim();
60
+ } catch (error) {
61
+ console.error('Error generating OpenAI response:', error);
62
+ return null;
63
+ }
64
+ }
65
+
66
+ async generateLocalResponse(prompt) {
67
+ try {
68
+ const command = `ollama run ${this.localModel} "${this.sanitizeInput(prompt)}"`;
69
+ const output = execSync(command, { encoding: 'utf-8' });
70
+ return output.trim();
71
+ } catch (error) {
72
+ console.error('Error generating local response:', error);
73
+ return null;
74
+ }
75
+ }
76
+
77
+ sanitizeInput(input) {
78
+ return input.replace(/"/g, '\\"').replace(/\n/g, ' ');
79
+ }
80
+ }
81
+
82
+ class AIDetector {
83
+ async detectAI() {
84
+ try {
85
+ const output = execSync('ollama list', { encoding: 'utf-8' });
86
+ const models = output.split('\n')
87
+ .filter(line => line.trim() && !line.startsWith('NAME'))
88
+ .map(line => line.split(' ')[0]);
89
+ if (models.length > 0) {
90
+ return {
91
+ name: 'ollama',
92
+ models: models
93
+ };
94
+ }
95
+ } catch (error) {
96
+ console.error('Error detecting Ollama:', error.message);
97
+ }
98
+ return null;
99
+ }
100
+ }
101
+
102
+ class PlantDates {
103
+ constructor(name, type, aiClient, language) {
104
+ this.name = name;
105
+ this.type = type;
106
+ this.aiClient = aiClient;
107
+ this.language = language;
108
+ this.plantInfo = null;
109
+ }
110
+
111
+ async generateInfo() {
112
+ console.log(chalk.bold('🔍🌿 Generating plant information...'));
113
+
114
+ try {
115
+ const plantInfoPrompt = `Provide a comprehensive summary for ${this.name} (${this.type}) including: Lighting, Watering, Temperature, Humidity, Soil, Fertilization, Pruning, and Propagation. Also, provide specific ranges for Lighting (in lux), Temperature (in Celsius), and Humidity (in percentage) in the format: "Lighting: X-Y lux, Temperature: A-B°C, Humidity: C-D%".`;
116
+
117
+ const plantInfoResponse = await this.getAIResponse(plantInfoPrompt);
118
+
119
+ // Extract ranges from the response
120
+ const lightingMatch = plantInfoResponse.match(/Lighting:\s*(\d+)-(\d+)\s*lux/i);
121
+ const temperatureMatch = plantInfoResponse.match(/Temperature:\s*(\d+)-(\d+)\s*°C/i);
122
+ const humidityMatch = plantInfoResponse.match(/Humidity:\s*(\d+)-(\d+)\s*%/i);
123
+
124
+ this.plantInfo = {
125
+ summary: plantInfoResponse,
126
+ lighting: lightingMatch ? { min: parseInt(lightingMatch[1]), max: parseInt(lightingMatch[2]) } : { min: 50, max: 700 },
127
+ temperature: temperatureMatch ? { min: parseInt(temperatureMatch[1]), max: parseInt(temperatureMatch[2]) } : { min: 18, max: 24 },
128
+ humidity: humidityMatch ? { min: parseInt(humidityMatch[1]), max: parseInt(humidityMatch[2]) } : { min: 40, max: 60 }
129
+ };
130
+
131
+ return this.plantInfo;
132
+ } catch (error) {
133
+ console.error('Error generating plant info:', error);
134
+ return null;
135
+ }
136
+ }
137
+
138
+ async getAIResponse(prompt) {
139
+ try {
140
+ const response = await this.aiClient.generateResponse(prompt, this.language);
141
+ await this.simulateTyping(response);
142
+ return response.trim();
143
+ } catch (error) {
144
+ console.error('Error getting AI response:', error);
145
+ return null;
146
+ }
147
+ }
148
+
149
+ async simulateTyping(text) {
150
+ for (let i = 0; i < text.length; i++) {
151
+ process.stdout.write(text[i]);
152
+ await new Promise(resolve => setTimeout(resolve, 10));
153
+ }
154
+ console.log('\n');
155
+ }
156
+
157
+ formatPlantInfo() {
158
+ if (!this.plantInfo) return 'No hay información disponible.';
159
+
160
+ return `
161
+ Rangos ideales:
162
+ 🌞 ${this.plantInfo.lighting.min}-${this.plantInfo.lighting.max} lux
163
+ 🌡️ ${this.plantInfo.temperature.min}-${this.plantInfo.temperature.max}°C
164
+ 💦 ${this.plantInfo.humidity.min}-${this.plantInfo.humidity.max}%
165
+ `;
166
+ }
167
+ }
168
+
169
+ class SmartPlant {
170
+ constructor() {
171
+ this.plantType = null;
172
+ this.plantName = '';
173
+ this.alerts = {};
174
+ this.language = 'en';
175
+ this.messages = null;
176
+ this.sensors = {
177
+ humidity: null,
178
+ light: null,
179
+ temperature: null
180
+ };
181
+ this.plantInfo = null;
182
+ this.platform = null;
183
+ this.serialPort = null;
184
+ this.aiClient = null;
185
+ this.aiDetector = new AIDetector();
186
+ this.historicalData = [];
187
+ this.isMonitoring = false;
188
+ this.hibernationMode = false;
189
+ this.hasSensors = false;
190
+ }
191
+
192
+ async init() {
193
+ this.messages = loadMessages(this.language);
194
+ }
195
+
196
+ async setLanguage(language) {
197
+ this.language = language;
198
+ this.messages = loadMessages(this.language);
199
+ }
200
+
201
+ welcome() {
202
+ console.log('\n' + chalk.bold(this.messages.general.welcome) + '\n');
203
+ }
204
+
205
+ async start() {
206
+ await this.init();
207
+ this.welcome();
208
+ await this.selectLanguage();
209
+ await this.selectPlatform();
210
+ await this.selectAIMethod();
211
+ console.log(); // Add a space after AI connection
212
+ await this.selectPlantType();
213
+ await this.setPlantName();
214
+ await this.generatePlantInfo();
215
+ await this.setupSensors();
216
+ this.setupAlerts();
217
+ this.startMonitoring();
218
+ }
219
+
220
+ async selectLanguage() {
221
+ const { language } = await inquirer.prompt({
222
+ type: 'list',
223
+ name: 'language',
224
+ message: 'Select language:',
225
+ choices: [
226
+ { name: 'English', value: 'en' },
227
+ { name: 'Español', value: 'es' },
228
+ { name: 'Français', value: 'fr' },
229
+ { name: 'Deutsch', value: 'de' },
230
+ { name: 'Italiano', value: 'it' },
231
+ { name: 'Português', value: 'pt' },
232
+ { name: 'Nederlands', value: 'nl' },
233
+ { name: 'Русский', value: 'ru' },
234
+ { name: '中文', value: 'zh' },
235
+ { name: '日本語', value: 'ja' }
236
+ ]
237
+ });
238
+
239
+ await this.setLanguage(language);
240
+ }
241
+
242
+ async selectPlatform() {
243
+ const { platform } = await inquirer.prompt({
244
+ type: 'list',
245
+ name: 'platform',
246
+ message: this.messages.general.selectPlatform,
247
+ choices: [
248
+ { name: 'Raspberry Pi', value: 'raspberry' },
249
+ { name: 'Arduino', value: 'arduino' }
250
+ ]
251
+ });
252
+
253
+ this.platform = platform;
254
+ await this.setupPlatform();
255
+ }
256
+
257
+ async setupPlatform() {
258
+ if (this.platform === 'raspberry') {
259
+ console.log(chalk.bold('Setting up Raspberry Pi...'));
260
+ this.hasSensors = true;
261
+ } else if (this.platform === 'arduino') {
262
+ console.log(chalk.bold('Setting up Arduino...'));
263
+ this.serialPort = new SerialPort({ path: '/dev/ttyACM0', baudRate: 9600 });
264
+ const parser = this.serialPort.pipe(new ReadlineParser({ delimiter: '\r\n' }));
265
+ parser.on('data', this.handleArduinoData.bind(this));
266
+ console.log('Arduino setup complete. Make sure arduino_dht22.ino is uploaded to your Arduino.');
267
+ this.hasSensors = true;
268
+ }
269
+ }
270
+
271
+ handleArduinoData(data) {
272
+ const [temperature, humidity, light] = data.split(',').map(Number);
273
+ this.sensors.temperature = temperature;
274
+ this.sensors.humidity = humidity;
275
+ this.sensors.light = light;
276
+ this.checkAlerts();
277
+ }
278
+
279
+ async selectAIMethod() {
280
+ const { method } = await inquirer.prompt({
281
+ type: 'list',
282
+ name: 'method',
283
+ message: 'Select AI method:',
284
+ choices: [
285
+ { name: 'Local (Ollama)', value: 'local' },
286
+ { name: 'OpenAI API', value: 'openai' }
287
+ ]
288
+ });
289
+
290
+ if (method === 'local') {
291
+ await this.selectLocalModel();
292
+ } else {
293
+ const { apiKey } = await inquirer.prompt({
294
+ type: 'input',
295
+ name: 'apiKey',
296
+ message: 'Enter your API key:'
297
+ });
298
+ this.aiClient = new AIClient(method, apiKey);
299
+ }
300
+ console.log(chalk.green('AI successfully connected! 🤖✨'));
301
+ }
302
+
303
+ async selectLocalModel() {
304
+ const aiModels = await this.aiDetector.detectAI();
305
+ if (aiModels && aiModels.models.length > 0) {
306
+ const { model } = await inquirer.prompt({
307
+ type: 'list',
308
+ name: 'model',
309
+ message: 'Select a local model:',
310
+ choices: aiModels.models
311
+ });
312
+ this.aiClient = new AIClient('local', null, model);
313
+ } else {
314
+ console.log('No local AI models found.');
315
+ }
316
+ }
317
+
318
+ async selectPlantType() {
319
+ const { type } = await inquirer.prompt({
320
+ type: 'list',
321
+ name: 'type',
322
+ message: this.messages.general.selectPlantType,
323
+ choices: [
324
+ { name: this.messages.general.indoor, value: 'indoor' },
325
+ { name: this.messages.general.outdoor, value: 'outdoor' }
326
+ ]
327
+ });
328
+
329
+ this.plantType = type;
330
+ }
331
+
332
+ async setPlantName() {
333
+ const { name } = await inquirer.prompt({
334
+ type: 'input',
335
+ name: 'name',
336
+ message: this.messages.general.enterPlantName
337
+ });
338
+ this.plantName = name;
339
+ }
340
+
341
+ async generatePlantInfo() {
342
+ const plantDates = new PlantDates(this.plantName, this.plantType, this.aiClient, this.language);
343
+ this.plantInfo = await plantDates.generateInfo();
344
+ if (this.plantInfo) {
345
+ this.idealRanges = plantDates.formatPlantInfo();
346
+ } else {
347
+ console.log('No se pudo generar la información de la planta.');
348
+ }
349
+ }
350
+
351
+ async setupSensors() {
352
+ console.log(chalk.bold(this.messages.general.settingUpSensors));
353
+ if (this.hasSensors) {
354
+ if (this.platform === 'raspberry') {
355
+ // For Raspberry Pi, we'll wait for actual sensor data
356
+ console.log('Waiting for sensor data from Raspberry Pi...');
357
+ }
358
+ // For Arduino, we're already set up in setupPlatform
359
+ } else {
360
+ console.log(chalk.yellow('No sensors detected. Running in simulation mode.'));
361
+ }
362
+ console.log(chalk.bold(this.messages.general.sensorsReady));
363
+ }
364
+
365
+ setupAlerts() {
366
+ if (this.plantInfo) {
367
+ this.alerts = {
368
+ humidity: { min: this.plantInfo.humidity.min, max: this.plantInfo.humidity.max },
369
+ light: { min: this.plantInfo.lighting.min, max: this.plantInfo.lighting.max },
370
+ temperature: { min: this.plantInfo.temperature.min, max: this.plantInfo.temperature.max }
371
+ };
372
+ console.log(this.messages.general.alertsSet);
373
+ } else {
374
+ console.log('No se pudieron configurar las alertas debido a la falta de información de la planta.');
375
+ }
376
+ }
377
+
378
+ calculatePercentage(value, min, max) {
379
+ if (value === null || value === undefined || isNaN(value)) {
380
+ return 0;
381
+ }
382
+ return Math.min(Math.max(((value - min) / (max - min)) * 100, 0), 100);
383
+ }
384
+
385
+ getHumidityEmoji(percentage) {
386
+ if (percentage <= 0) return "🍂";
387
+ if (percentage <= 30) return "🍂";
388
+ if (percentage <= 60) return "🌿";
389
+ if (percentage <= 80) return "💧";
390
+ return "🌊";
391
+ }
392
+
393
+ getLightEmoji(percentage) {
394
+ if (percentage <= 0) return "🌑";
395
+ if (percentage <= 30) return "🌑";
396
+ if (percentage <= 60) return "🌥";
397
+ return "🌞";
398
+ }
399
+
400
+ getTemperatureEmoji(percentage) {
401
+ if (percentage <= 0) return "🧊";
402
+ if (percentage <= 30) return "🧊";
403
+ if (percentage <= 80) return "🌡️";
404
+ return "🔥";
405
+ }
406
+
407
+ getHappinessEmoji(averagePercentage) {
408
+ if (averagePercentage <= 0) return '😵';
409
+ if (averagePercentage >= 90) return '🤩';
410
+ if (averagePercentage >= 75) return '😊';
411
+ if (averagePercentage >= 60) return '😐';
412
+ if (averagePercentage >= 45) return '😞';
413
+ if (averagePercentage >= 30) return '😖';
414
+ return '🥵';
415
+ }
416
+
417
+ logPlantStatus() {
418
+ const allSensorsZero = Object.values(this.sensors).every(value => value === 0 || value === null || value === undefined);
419
+
420
+ if (!this.hasSensors || allSensorsZero) {
421
+ console.log(chalk.yellow('🔔 Warning: No data input or sensors are disconnected.'));
422
+ console.log('😵 | Lighting: 🌑 (0%) | Temperature: 🧊 (0.0°C) | Humidity: 🍂 (0%)');
423
+ return;
424
+ }
425
+
426
+ const humidityPercentage = this.calculatePercentage(this.sensors.humidity, this.alerts.humidity.min, this.alerts.humidity.max);
427
+ const lightPercentage = this.calculatePercentage(this.sensors.light, this.alerts.light.min, this.alerts.light.max);
428
+ const temperaturePercentage = this.calculatePercentage(this.sensors.temperature, this.alerts.temperature.min, this.alerts.temperature.max);
429
+
430
+ const averagePercentage = (humidityPercentage + lightPercentage + temperaturePercentage) / 3;
431
+
432
+ const happinessEmoji = this.getHappinessEmoji(averagePercentage);
433
+ const humidityEmoji = this.getHumidityEmoji(humidityPercentage);
434
+ const lightEmoji = this.getLightEmoji(lightPercentage);
435
+ const temperatureEmoji = this.getTemperatureEmoji(temperaturePercentage);
436
+
437
+ const status = `${happinessEmoji} | Lighting: ${lightEmoji} (${lightPercentage.toFixed(0)}%) | Temperature: ${temperatureEmoji} (${this.sensors.temperature?.toFixed(1) || 0.0}°C) | Humidity: ${humidityEmoji} (${humidityPercentage.toFixed(0)}%)`;
438
+ console.log(status);
439
+
440
+ this.historicalData.push({
441
+ timestamp: new Date(),
442
+ humidity: this.sensors.humidity,
443
+ light: this.sensors.light,
444
+ temperature: this.sensors.temperature
445
+ });
446
+
447
+ this.predictCriticalState();
448
+ }
449
+
450
+ predictCriticalState() {
451
+ if (this.historicalData.length < 10) return; // Need more data for prediction
452
+
453
+ const recentData = this.historicalData.slice(-10);
454
+ const trends = {
455
+ humidity: this.calculateTrend(recentData.map(d => d.humidity)),
456
+ light: this.calculateTrend(recentData.map(d => d.light)),
457
+ temperature: this.calculateTrend(recentData.map(d => d.temperature))
458
+ };
459
+
460
+ for (const [sensor, trend] of Object.entries(trends)) {
461
+ if (Math.abs(trend) > 0.5) { // Significant trend detected
462
+ const direction = trend > 0 ? 'increasing' : 'decreasing';
463
+ console.log(chalk.yellow.bold(`🔔 Warning: ${sensor} is ${direction} rapidly. Consider taking action.`));
464
+ }
465
+ }
466
+ }
467
+
468
+ calculateTrend(data) {
469
+ const n = data.length;
470
+ const sum_x = n * (n + 1) / 2;
471
+ const sum_y = data.reduce((a, b) => a + b, 0);
472
+ const sum_xy = data.reduce((sum, y, i) => sum + y * (i + 1), 0);
473
+ const sum_xx = n * (n + 1) * (2 * n + 1) / 6;
474
+
475
+ const slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
476
+ return slope;
477
+ }
478
+
479
+ startMonitoring() {
480
+ console.log(this.messages.general.startMonitoring);
481
+ this.isMonitoring = true;
482
+ this.monitoringInterval = setInterval(() => {
483
+ if (!this.hibernationMode) {
484
+ this.logPlantStatus();
485
+ }
486
+ }, 60000); // Log every minute
487
+
488
+ // Enable keypress detection
489
+ process.stdin.setRawMode(true);
490
+ process.stdin.resume();
491
+ process.stdin.setEncoding('utf8');
492
+ process.stdin.on('data', (key) => {
493
+ if (key === '\u0003') { // Ctrl+C
494
+ process.exit();
495
+ } else if (key === '\u000F') { // Ctrl+O
496
+ this.pauseMonitoring();
497
+ }
498
+ });
499
+ }
500
+
501
+ pauseMonitoring() {
502
+ clearInterval(this.monitoringInterval);
503
+ this.isMonitoring = false;
504
+ this.displaySensorSettingsMenu();
505
+ }
506
+
507
+ async displaySensorSettingsMenu() {
508
+ const choices = [
509
+ 'Ajustar configuración de sensores',
510
+ 'Activar/Desactivar modo de hibernación',
511
+ 'Volver a iniciar monitoreo',
512
+ 'Salir'
513
+ ];
514
+
515
+ const { option } = await inquirer.prompt({
516
+ type: 'list',
517
+ name: 'option',
518
+ message: 'Monitoreo detenido. ¿Qué deseas hacer?',
519
+ choices: choices,
520
+ });
521
+
522
+ switch (option) {
523
+ case choices[0]:
524
+ await this.adjustSensorSettings();
525
+ break;
526
+ case choices[1]:
527
+ await this.toggleHibernation();
528
+ break;
529
+ case choices[2]:
530
+ this.startMonitoring();
531
+ break;
532
+ case choices[3]:
533
+ console.log('Saliendo del menú de ajustes.');
534
+ process.exit();
535
+ }
536
+ }
537
+
538
+ async adjustSensorSettings() {
539
+ const sensorSettings = await inquirer.prompt([
540
+ {
541
+ type: 'input',
542
+ name: 'humidity',
543
+ message: `Humedad ideal (actual: ${this.plantInfo.humidity.min}-${this.plantInfo.humidity.max}%):`,
544
+ default: `${this.plantInfo.humidity.min}-${this.plantInfo.humidity.max}`,
545
+ },
546
+ {
547
+ type: 'input',
548
+ name: 'temperature',
549
+ message: `Temperatura ideal (actual: ${this.plantInfo.temperature.min}-${this.plantInfo.temperature.max}°C):`,
550
+ default: `${this.plantInfo.temperature.min}-${this.plantInfo.temperature.max}`,
551
+ },
552
+ {
553
+ type: 'input',
554
+ name: 'light',
555
+ message: `Luz ideal (actual: ${this.plantInfo.lighting.min}-${this.plantInfo.lighting.max} lux):`,
556
+ default: `${this.plantInfo.lighting.min}-${this.plantInfo.lighting.max}`,
557
+ },
558
+ ]);
559
+
560
+ this.plantInfo.humidity = this.parseRange(sensorSettings.humidity);
561
+ this.plantInfo.temperature = this.parseRange(sensorSettings.temperature);
562
+ this.plantInfo.lighting = this.parseRange(sensorSettings.light);
563
+
564
+ this.setupAlerts();
565
+
566
+ console.log(`Nuevos valores ajustados:
567
+ Humedad: ${this.plantInfo.humidity.min}-${this.plantInfo.humidity.max}%
568
+ Temperatura: ${this.plantInfo.temperature.min}-${this.plantInfo.temperature.max}°C
569
+ Luz: ${this.plantInfo.lighting.min}-${this.plantInfo.lighting.max} lux`);
570
+ }
571
+
572
+ parseRange(rangeString) {
573
+ const [min, max] = rangeString.split('-').map(Number);
574
+ return { min, max };
575
+ }
576
+
577
+ async toggleHibernation() {
578
+ const { hibernation } = await inquirer.prompt({
579
+ type: 'confirm',
580
+ name: 'hibernation',
581
+ message: '¿Activar modo de hibernación?',
582
+ default: false,
583
+ });
584
+
585
+ this.hibernationMode = hibernation;
586
+ console.log(`Modo de hibernación ${this.hibernationMode ? 'activado' : 'desactivado'}.`);
587
+ }
588
+
589
+ checkAlerts() {
590
+ if (!this.hasSensors) return;
591
+
592
+ for (const [sensor, value] of Object.entries(this.sensors)) {
593
+ if (value === null || value === undefined || isNaN(value)) continue;
594
+ if (value < this.alerts[sensor].min) {
595
+ console.log(chalk.red(this.messages.alerts[sensor].low.replace('{value}', value.toFixed(2))));
596
+ } else if (value > this.alerts[sensor].max) {
597
+ console.log(chalk.red(this.messages.alerts[sensor].high.replace('{value}', value.toFixed(2))));
598
+ }
599
+ }
600
+ }
601
+ }
602
+
603
+ const smartPlant = new SmartPlant();
604
+ smartPlant.start().catch(console.error);
605
+
606
+ export default SmartPlant;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "smartplant",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A library for managing plant care with alerts and multi-language support.",
5
- "main": "comingsoon.js",
5
+ "main": "main.js",
6
6
  "type": "module",
7
7
  "dependencies": {
8
8
  "@serialport/parser-readline": "^12.0.0",
@@ -110,7 +110,7 @@
110
110
  ]
111
111
  },
112
112
  "scripts": {
113
- "start": "node index.js",
113
+ "start": "node main.js",
114
114
  "update-version": "changeset && changeset version",
115
115
  "push": "git add . && cz && git push -f origin $@",
116
116
  "push:main": "pnpm push main"
package/comingsoon.js DELETED
@@ -1,6 +0,0 @@
1
- console.log(`COMING SOON!!
2
- View development progress at https://github.com/pigeonposse/smartplant
3
-
4
- Funding: https://github.com/sponsors/pigeonposse
5
- Repo: https://github.com/pigeonposse/smartplant
6
- `)