diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d9ebdb0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: Bug +assignees: nicodinh + +--- + +## *Who* is the bug affecting? + + +## *What* is affected by this bug? + + +## *When* does this occur? + + +## *Where* on the platform does it happen? + + + +## *How* do we replicate the issue? + + + +## Expected behavior (i.e. solution) + + + +## Other Comments/Screenshots \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..6d34f46 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Discord Community Chat + url: https://discord.gg/PvDca3 + about: Please ask and answer questions here. +# - name: GitHub Security Bug Bounty +# url: https://bounty.github.com/ +# about: Please report security vulnerabilities here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..9349803 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: Feature +assignees: nicodinh + +--- + +## **Is your feature request related to a problem? Please describe.** + + +## **Describe the solution you'd like** + + +## **Describe alternatives you've considered** + + +## **Additional context** + \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0c76a91 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,28 @@ + + +## Description + + +## Motivation and Context + + + +## How Has This Been Tested? + + + + +## Screenshots (if appropriate): + +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + +## Checklist: + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. \ No newline at end of file diff --git a/components/BatteryChart.js b/components/BatteryChart.js index af49e06..34953f8 100644 --- a/components/BatteryChart.js +++ b/components/BatteryChart.js @@ -1,12 +1,30 @@ import React from 'react' -import { VictoryChart, VictoryLine, VictoryTheme, VictoryLegend } from 'victory' +import { + VictoryChart, + VictoryLine, + VictoryTheme, + VictoryLegend, + VictoryContainer +} from 'victory' +import { useStoreState } from 'easy-peasy' const BatteryChart = () => { + const { chartValues, values } = useStoreState(state => state.battery) + return (
+ } > { border: { stroke: 'black' }, title: { fontSize: 16 } }} - data={[{ name: 'Charge. %', symbol: { fill: 'green' } }]} + data={[{ name: 'Charge. %', symbol: { fill: 'blue' } }]} /> 1 ? chartValues : []} />
diff --git a/components/Bluetooth.js b/components/Bluetooth.js deleted file mode 100644 index c06629f..0000000 --- a/components/Bluetooth.js +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react' -import { useStoreState } from 'easy-peasy' -import { celciusToFahrenheit } from '../lib/celciusToFahrenheit' - -let myCharacteristicNotify = null - -class Bluetooth extends React.Component { - constructor (props) { - super(props) - this.onButtonClick = this.onButtonClick.bind(this) - this.handleNotifications = this.handleNotifications.bind(this) - } - - componentWillUnmount () { - myCharacteristicNotify.removeEventListener( - 'characteristicvaluechanged', - this.handleNotifications, - false - ) - clearInterval(this.interval) - } - - state = { - deviceName: '', - deviceID: '', - deviceConnected: false, - error: '', - service: '0000fee9-0000-1000-8000-00805f9b34fb', - characteristicNotify: 'd44bc439-abfd-45a2-b575-925416129601', - characteristic: 'd44bc439-abfd-45a2-b575-925416129600', - temperaturePrefix: 'aa8f', - batteryPrefix: 'aa8e', - temperatureC: 3, - temperatureF: 37, - battery: null, - isSectorAndBatteryInCharge: false, // button on - isBatteryDischarge: false, // button on - isSectorAndBatteryOff: false, // button off - batteryStatus: null - } - - handleNotifications (event) { - let value = event.target.value - let a = [] - - for (let i = 0; i < value.byteLength; i++) { - a.push(('00' + value.getUint8(i).toString(16)).slice(-2)) - } - - const hexString = a.join('') - - if (hexString.substr(0, 4) === 'aa8f') { - const hex2dec = parseInt(a[2], 16) - const celcius = hex2dec / 10 - - this.setState({ - temperatureC: celcius, - temperatureF: celciusToFahrenheit(celcius) - }) - } - - if (hexString.substr(0, 4) === 'aa8e') { - const binaryBattery = parseInt(a[2], 16) - .toString(2) - .padStart(8, '0') - - if (binaryBattery === '10000000') { - this.setState(prevState => ({ - isSectorAndBatteryInCharge: false, - isBatteryDischarge: false, - isSectorAndBatteryOff: true, - batteryStatus: 'Plugged in main', - battery: null - })) - } - - if (binaryBattery < '10000000') { - this.setState({ - isSectorAndBatteryInCharge: false, - isBatteryDischarge: true, - isSectorAndBatteryOff: false, - batteryStatus: 'Discharging', - battery: - parseInt(binaryBattery.substr(1), 2) >= 100 - ? 100 - : parseInt(binaryBattery.substr(1), 2) - }) - } - - if (binaryBattery > '10000000') { - this.setState({ - isSectorAndBatteryInCharge: true, - isBatteryDischarge: false, - isSectorAndBatteryOff: false, - batteryStatus: 'In Charge', - battery: - parseInt(binaryBattery.substr(1), 2) >= 100 - ? 100 - : parseInt(binaryBattery.substr(1), 2) - }) - } - } - } - async onButtonClick () { - const { - lifeinaboxName, - lifeinaboxService, - lifeinaboxCharacteristicNotify, - lifeinaboxCharacteristic - } = useStoreState(state => state.device) - try { - const device = await navigator.bluetooth.requestDevice({ - filters: [{ name: lifeinaboxName }], - optionalServices: [lifeinaboxService] - }) - const server = await device.gatt.connect() - const deviceConnected = server.connected ? 'yes' : 'no' - console.log(device.name) - console.log(device.id) - this.setState({ - deviceName: device.name, - deviceID: device.id, - deviceConnected - }) - const service = await server.getPrimaryService(lifeinaboxService) - myCharacteristicNotify = await service.getCharacteristic( - lifeinaboxCharacteristicNotify - ) - await myCharacteristicNotify.startNotifications() - myCharacteristicNotify.addEventListener( - 'characteristicvaluechanged', - this.handleNotifications - ) - const commandTMP1H = new Uint8Array([0xaa, 0x8f, 0x01, 0x55]) - const commandBATTERY = new Uint8Array([0xaa, 0x8e, 0xff, 0x55]) - const myCharacteristic = await service.getCharacteristic( - this.state.characteristic - ) - await myCharacteristic.writeValue(commandTMP1H) - await myCharacteristic.writeValue(commandBATTERY) - this.interval = setInterval(async () => { - await myCharacteristic.writeValue(commandTMP1H) - await myCharacteristic.writeValue(commandBATTERY) - console.log('interval') - }, 1000) //600000 - } catch (error) { - console.log('Argh! ' + error) - this.setState({ - error - }) - } - } - - render () { - return ( -
-
-
- - - -
- - {this.state.deviceConnected ? this.state.temperatureC : null} - -
- - {this.state.deviceConnected && !this.state.isSectorAndBatteryOff - ? Math.round(this.state.battery) - : this.state.deviceConnected && this.state.isSectorAndBatteryOff - ? 'Battery is off' - : null} - -
-
- - {this.state.deviceConnected ? this.state.batteryStatus : null} -
- ) - } -} - -export default Bluetooth diff --git a/components/FindButton.js b/components/FindButton.js new file mode 100644 index 0000000..8305f00 --- /dev/null +++ b/components/FindButton.js @@ -0,0 +1,169 @@ +import React from 'react' +import { useStoreState, useStoreActions } from 'easy-peasy' +import { lastBatteryBuffer, lastTemperatureBuffer } from '../lib/' + +let myCharacteristicNotify = null +let interval = null + +const FindButton = () => { + // Device Model + const { + lifeinaboxName, + lifeinaboxService, + lifeinaboxCharacteristicNotify, + lifeinaboxCharacteristic, + isConnected + } = useStoreState(state => state.device) + + const { updateDeviceID, updateIsConnected } = useStoreActions( + actions => actions.device + ) + const resetDevice = useStoreActions(actions => actions.device.reset) + + // Temperature Model + const addTemperature = useStoreActions( + actions => actions.temperature.addValue + ) + const resetTemperature = useStoreActions(actions => actions.temperature.reset) + // Battery Model + const addBatteryValue = useStoreActions(actions => actions.battery.addValue) + const updateBatteryStatus = useStoreActions( + actions => actions.battery.updateStatus + ) + const resetBattery = useStoreActions(actions => actions.battery.reset) + + // Settings Model + const { pollInterval, unit } = useStoreState(state => state.settings) + + const handleNotifications = event => { + let value = event.target.value + let a = [] + + for (let i = 0; i < value.byteLength; i++) { + a.push(('00' + value.getUint8(i).toString(16)).slice(-2)) + } + + const hexString = a.join('') + + if (hexString.substr(0, 4) === 'aa8f') { + const hex2dec = parseInt(a[2], 16) + const celcius = hex2dec / 10 + + // console.log(celcius) + addTemperature(celcius) + } + + if (hexString.substr(0, 4) === 'aa8e') { + const binaryBattery = parseInt(a[2], 16) + .toString(2) + .padStart(8, '0') + + if (binaryBattery === '10000000') { + updateBatteryStatus('Plugged in main') + } + + if (binaryBattery < '10000000') { + updateBatteryStatus('Discharging') + addBatteryValue( + parseInt(binaryBattery.substr(1), 2) >= 100 + ? 100 + : parseInt(binaryBattery.substr(1), 2) + ) + } + + if (binaryBattery > '10000000') { + updateBatteryStatus('In Charge') + addBatteryValue( + parseInt(binaryBattery.substr(1), 2) >= 100 + ? 100 + : parseInt(binaryBattery.substr(1), 2) + ) + } + } + } + + const onClickDiscoverButton = async e => { + e.preventDefault() + + try { + const device = await navigator.bluetooth.requestDevice({ + filters: [{ name: lifeinaboxName }], + optionalServices: [lifeinaboxService] + }) + const server = await device.gatt.connect() + + updateDeviceID(device.id) + updateIsConnected(server.connected) + + const service = await server.getPrimaryService(lifeinaboxService) + myCharacteristicNotify = await service.getCharacteristic( + lifeinaboxCharacteristicNotify + ) + await myCharacteristicNotify.startNotifications() + myCharacteristicNotify.addEventListener( + 'characteristicvaluechanged', + handleNotifications + ) + + const myCharacteristic = await service.getCharacteristic( + lifeinaboxCharacteristic + ) + + interval = setInterval(async () => { + await myCharacteristic.writeValue(lastTemperatureBuffer) + await myCharacteristic.writeValue(lastBatteryBuffer) + }, pollInterval) + } catch (error) { + console.error(error) + } + } + + const onClickDisconnectButton = async e => { + e.preventDefault() + + // remove listeners + if (myCharacteristicNotify && interval) { + myCharacteristicNotify.removeEventListener( + 'characteristicvaluechanged', + handleNotifications, + false + ) + clearInterval(interval) + + // and clean them + myCharacteristicNotify = null + interval = null + } + + // and reset redux + try { + await resetBattery() + await resetDevice() + await resetTemperature() + } catch (error) { + console.log(error) + } + } + + return ( + <> + + + ) +} + +export { FindButton } diff --git a/components/LifeinaBoxStats.js b/components/LifeinaBoxStats.js index 16c764b..b7b1629 100644 --- a/components/LifeinaBoxStats.js +++ b/components/LifeinaBoxStats.js @@ -4,6 +4,7 @@ import { useStoreState } from 'easy-peasy' const LifeinaBoxStats = () => { const temperatureValue = useStoreState(state => state.temperature.value) const batteryValue = useStoreState(state => state.battery.value) + const batteryStatus = useStoreState(state => state.battery.status) return (
@@ -24,6 +25,11 @@ const LifeinaBoxStats = () => { {`${batteryValue} %`} ) : null} + {batteryStatus ? ( + + {`${batteryStatus}`} + + ) : null}
) diff --git a/components/Nav.js b/components/Nav.js new file mode 100644 index 0000000..ad977c6 --- /dev/null +++ b/components/Nav.js @@ -0,0 +1,44 @@ +import React from 'react' +import Link from 'next/link' +import { FindButton } from './FindButton' + +const Nav = () => { + // current page class color: text-teal-200 + + return ( + + ) +} + +export { Nav } diff --git a/components/TemperatureChart.js b/components/TemperatureChart.js index 300a131..26782e8 100644 --- a/components/TemperatureChart.js +++ b/components/TemperatureChart.js @@ -1,5 +1,11 @@ import React from 'react' -import { VictoryChart, VictoryLine, VictoryTheme, VictoryLegend } from 'victory' +import { + VictoryChart, + VictoryLine, + VictoryTheme, + VictoryLegend, + VictoryContainer +} from 'victory' import { useStoreState } from 'easy-peasy' const TemperatureChart = () => { @@ -10,6 +16,15 @@ const TemperatureChart = () => { + } > { /> 11 ? chartValues : []} data={values.length > 1 ? chartValues : []} /> diff --git a/components/index.js b/components/index.js index 85b39a8..65bdd12 100644 --- a/components/index.js +++ b/components/index.js @@ -1,3 +1,4 @@ export * from './BatteryChart' export * from './LifeinaBoxStats' export * from './TemperatureChart' +export * from './Nav' diff --git a/components/Menu.js b/i18n/enGB.js similarity index 100% rename from components/Menu.js rename to i18n/enGB.js diff --git a/i18n/frFR.js b/i18n/frFR.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/handleNotifications.js b/lib/handleNotifications.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/onClickDisconnectButton.js b/lib/onClickDisconnectButton.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/onClickDiscoverButton.js b/lib/onClickDiscoverButton.js new file mode 100644 index 0000000..e69de29 diff --git a/model/battery-model.js b/model/battery-model.js index a097fe6..1b80cba 100644 --- a/model/battery-model.js +++ b/model/battery-model.js @@ -1,7 +1,34 @@ -import { action } from 'easy-peasy' +import { action, computed } from 'easy-peasy' +import { nth } from 'lodash' const batteryModel = { - values: [] + values: [], + status: '', + reset: action(state => { + ;(state.values = []), (state.status = '') + }), + addValue: action((state, payload) => { + state.values.push(payload) + }), + updateStatus: action((state, payload) => { + state.status = payload + }), + value: computed(state => nth(state.values, -1)), + chartValues: computed(state => { + return [ + { x: 0, y: nth(state.values, -1) }, + { x: -1, y: nth(state.values, -2) || 0 }, + { x: -2, y: nth(state.values, -3) || 0 }, + { x: -3, y: nth(state.values, -4) || 0 }, + { x: -4, y: nth(state.values, -5) || 0 }, + { x: -5, y: nth(state.values, -6) || 0 }, + { x: -6, y: nth(state.values, -7) || 0 }, + { x: -7, y: nth(state.values, -8) || 0 }, + { x: -8, y: nth(state.values, -9) || 0 }, + { x: -9, y: nth(state.values, -10) || 0 }, + { x: -10, y: nth(state.values, -11) || 0 } + ] + }) } export default batteryModel diff --git a/model/device-model.js b/model/device-model.js index 379a050..ae2e8db 100644 --- a/model/device-model.js +++ b/model/device-model.js @@ -7,6 +7,9 @@ const deviceModel = { lifeinaboxCharacteristic: 'd44bc439-abfd-45a2-b575-925416129600', deviceID: '', isConnected: false, + reset: action(state => { + ;(state.deviceID = ''), (state.isConnected = false) + }), updateDeviceID: action((state, payload) => { state.deviceID = payload }), diff --git a/model/index.js b/model/index.js index d54c72a..9ccde53 100644 --- a/model/index.js +++ b/model/index.js @@ -2,12 +2,14 @@ import temperatureModel from './temperature-model' import batteryModel from './battery-model' import settingsModel from './settings-model' import deviceModel from './device-model' +import uiModel from './ui-model' const storeModel = { temperature: temperatureModel, battery: batteryModel, settings: settingsModel, - device: deviceModel + device: deviceModel, + ui: uiModel // devices: [ // {1: {temperature: temperatureModel}}, // {2: {temperature: temperatureModel}}, diff --git a/model/temperature-model.js b/model/temperature-model.js index 75dc78b..f2950e5 100644 --- a/model/temperature-model.js +++ b/model/temperature-model.js @@ -3,6 +3,9 @@ import { nth } from 'lodash' const temperatureModel = { values: [], + reset: action(state => { + state.values = [] + }), addValue: action((state, payload) => { state.values.push(payload) }), diff --git a/model/ui-model.js b/model/ui-model.js new file mode 100644 index 0000000..9c7a724 --- /dev/null +++ b/model/ui-model.js @@ -0,0 +1,7 @@ +import { action } from 'easy-peasy' + +const uiModel = { + currentNavigationItem: '' +} + +export default uiModel diff --git a/package.json b/package.json index 9741007..0fa4566 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "prop-types": "^15.7.2", "react": "latest", "react-dom": "latest", + "styled-components": "^5.0.1", "victory": "^34.1.1", "yup": "^0.28.1" }, diff --git a/pages/chat-with-us.js b/pages/chat-with-us.js index 6a2991f..428e597 100644 --- a/pages/chat-with-us.js +++ b/pages/chat-with-us.js @@ -1,50 +1,10 @@ import React from 'react' import '../styles/index.css' +import { Nav } from '../components/' export default () => ( <> - +