Compare commits
10 Commits
bc548b767a
...
47f43132c6
| Author | SHA1 | Date |
|---|---|---|
|
|
47f43132c6 | |
|
|
4f02716575 | |
|
|
8f575e1c70 | |
|
|
e503b91267 | |
|
|
425b7eff91 | |
|
|
c080889e1f | |
|
|
0f05715333 | |
|
|
235244d76a | |
|
|
b1d0d2d681 | |
|
|
eb0f8caeb4 |
Binary file not shown.
102
REAME.md
102
REAME.md
|
|
@ -1,33 +1,30 @@
|
||||||
# RASPBERRY PI ALS ZENTRALRECHNER FUER BIENENSTOCKUEBERWACHUNG
|
# RASPBERRY PI ALS ZENTRALRECHNER FUER BIENENSTOCKUEBERWACHUNG
|
||||||
|
|
||||||
## Hardware Variante A
|
## Hardware
|
||||||
- Raspberry Pi Zero W
|
- Raspberry Pi Zero W
|
||||||
- 3G Stick Huawei E3531
|
- 3G/4G Stick Huawei E3533
|
||||||
|
|
||||||
## Hardware Variante B (Kurt Jakob)
|
|
||||||
- Raspberry Pi 3 Model B
|
|
||||||
- Itead Raspberry Pi GSM/GPRS Board (SIM800)
|
|
||||||
- ModMyPi Itead GSM Board Gehäuse
|
|
||||||
|
|
||||||
## INSTALLATION
|
## INSTALLATION
|
||||||
|
|
||||||
### Grundinstallation
|
### Grundinstallation
|
||||||
```
|
```
|
||||||
Image: 2017-09-07-raspbian-stretch-lite.zip von https://www.raspberrypi.org/downloads/raspbian/
|
Image: 2018-04-18-raspbian-stretch-lite.zip von https://www.raspberrypi.org/downloads/raspbian/
|
||||||
|
|
||||||
Installation auf SD:
|
Installation auf SD:
|
||||||
# unzip -p 2017-09-07-raspbian-stretch-lite.zip |dd of=/dev/sdXXX bs=4M conv=fsync
|
# unzip -p 2018-04-18-raspbian-stretch-lite.zip |dd of=/dev/sdXXX bs=4M conv=fsync
|
||||||
|
|
||||||
# raspi-config
|
# raspi-config
|
||||||
- Change User Password: meielis-...
|
- Change User Password: meielis-b...
|
||||||
- Hostname: beielipi
|
- Hostname: beielipi
|
||||||
|
- enable predicatble network interface names
|
||||||
- locale: en_US.utf8 (auch Default)
|
- locale: en_US.utf8 (auch Default)
|
||||||
- locale: de_CH.utf8
|
- locale: de_CH.utf8
|
||||||
- Timezone: Europe/Zurich
|
- Timezone: Europe/Zurich
|
||||||
- Change Wifi Country: CH
|
- Change Wifi Country: CH
|
||||||
- Keyboard: Generic 105-key (Intl), German (Switzerland)
|
- Keyboard: Generic 105-key (Intl), German (Switzerland)
|
||||||
- Enable SSH Server
|
- Enable SSH Server
|
||||||
- Fuer Variante A: WLAN einrichten (siehe "per WLAN")
|
- WLAN einrichten (siehe "per WLAN")
|
||||||
|
- reboot
|
||||||
|
|
||||||
# apt-get update
|
# apt-get update
|
||||||
# apt-get upgrade
|
# apt-get upgrade
|
||||||
|
|
@ -43,9 +40,10 @@ Software installieren:
|
||||||
# apt-get install ntpdate
|
# apt-get install ntpdate
|
||||||
# apt-get install dnsmasq
|
# apt-get install dnsmasq
|
||||||
# apt-get install python-bottle
|
# apt-get install python-bottle
|
||||||
|
# apt-get install python-pip
|
||||||
|
# apt-get install libglib2.0-dev
|
||||||
|
|
||||||
Firmware-Update:
|
# pip install bluepy
|
||||||
# rpi-update
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Zugang ermöglichen
|
### Zugang ermöglichen
|
||||||
|
|
@ -61,18 +59,41 @@ network={
|
||||||
ssid="haerdoepfu27" psk="XXXXXXXXXXXX"
|
ssid="haerdoepfu27" psk="XXXXXXXXXXXX"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#### per LAN
|
|
||||||
|
### Boot Config anpassen
|
||||||
|
|
||||||
|
in /boot/config.txt
|
||||||
|
|
||||||
```
|
```
|
||||||
root@beielipi:~# cat /etc/network/interfaces.d/eth0
|
dtparam=audio=off
|
||||||
auto eth0
|
|
||||||
iface eth0 inet static
|
|
||||||
address 192.168.0.50
|
|
||||||
netmask 255.255.255.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Variante A
|
### Strom sparen
|
||||||
|
|
||||||
|
```
|
||||||
|
/etc/rc.local einfuegen:
|
||||||
|
# Disable HDMI to save power
|
||||||
|
# Joerg Lehmann, 2.4.2018
|
||||||
|
/usr/bin/tvservice -o
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
```
|
||||||
|
|
||||||
|
=== Blacklist Modules
|
||||||
|
|
||||||
|
```
|
||||||
|
/etc/modprobe.d/raspi-blacklist.conf:
|
||||||
|
blacklist spi-bcm2708
|
||||||
|
blacklist i2c-bcm2708
|
||||||
|
blacklist huawei_cdc_ncm
|
||||||
|
blacklist sr_mod
|
||||||
|
blacklist cdrom
|
||||||
|
blacklist usb-storage
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Einrichten GSM
|
||||||
```
|
```
|
||||||
Einrichten GSM:
|
|
||||||
/etc/usb_modeswitch.d/12d1:157d:
|
/etc/usb_modeswitch.d/12d1:157d:
|
||||||
#Huawei E3533
|
#Huawei E3533
|
||||||
TargetVendor=0x12d1
|
TargetVendor=0x12d1
|
||||||
|
|
@ -81,10 +102,14 @@ MessageContent="55534243123456780000000000000011063000000000010000000000000000"
|
||||||
NoMBIMCheck=1
|
NoMBIMCheck=1
|
||||||
|
|
||||||
/etc/usb_modeswitch.conf:
|
/etc/usb_modeswitch.conf:
|
||||||
|
DisableMBIMGlobal=1
|
||||||
SetStorageDelay=8
|
SetStorageDelay=8
|
||||||
|
|
||||||
Workaround fuer USB disconnects:
|
Workaround fuer USB disconnects:
|
||||||
|
|
||||||
|
In /boot/cmdline.txt folgendes anhaengen:
|
||||||
|
dwc_otg.speed=1
|
||||||
|
|
||||||
/etc/cron.d/reset_usb:
|
/etc/cron.d/reset_usb:
|
||||||
# Reset USB if device names disappear
|
# Reset USB if device names disappear
|
||||||
* * * * * root [ ! -c /dev/ttyUSB0 ] && /usr/local/bin/usb_off_on.sh 2>/dev/null
|
* * * * * root [ ! -c /dev/ttyUSB0 ] && /usr/local/bin/usb_off_on.sh 2>/dev/null
|
||||||
|
|
@ -116,22 +141,6 @@ OK AT+CPMS="SM";+CNMI=2,0,0,2,1
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Variante B
|
|
||||||
```
|
|
||||||
# systemctl disable hciuart.service
|
|
||||||
|
|
||||||
# cat /boot/config.txt
|
|
||||||
...
|
|
||||||
# Wegen GSM Modul
|
|
||||||
dtoverlay=pi3-miniuart-bt
|
|
||||||
#dtoverlay=pi3-disable-bt
|
|
||||||
enable_uart=1
|
|
||||||
===
|
|
||||||
|
|
||||||
# cat /boot/cmdline.txt
|
|
||||||
dwc_otg.lpm_enable=0 root=PARTUUID=61612258-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
|
|
||||||
===
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test SMS-Versand:
|
### Test SMS-Versand:
|
||||||
```
|
```
|
||||||
|
|
@ -167,15 +176,22 @@ beieli@beielipi:~ $ exit
|
||||||
# for i in $(find . -type f); do echo cp $i /$i ; done
|
# for i in $(find . -type f); do echo cp $i /$i ; done
|
||||||
# for i in $(find . -type f); do cp $i /$i ; done
|
# for i in $(find . -type f); do cp $i /$i ; done
|
||||||
|
|
||||||
Fuer Variante A wieder loeschen:
|
|
||||||
# rm /etc/cron.d/gsm_poweron
|
|
||||||
|
|
||||||
# systemctl enable beielimon
|
# systemctl enable beielimon
|
||||||
|
# systemctl enable btmon
|
||||||
# systemctl enable smsmon
|
# systemctl enable smsmon
|
||||||
Fuer Variante B:
|
# systemctl disable hostapd
|
||||||
# systemctl disable dhcpcd
|
# systemctl disable fakedns
|
||||||
|
# systemctl disable dnsmasq
|
||||||
|
# systemctl disable avahi-daemon
|
||||||
|
# systemctl disable triggerhappy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/etc/fstab anpassen:
|
||||||
|
tmpfs /home/beieli/bt-readings tmpfs nodev,nosuid,uid=1001,gid=2000,size=4M 0 0
|
||||||
|
|
||||||
|
# mkdir /home/beieli/bt-readings
|
||||||
|
|
||||||
Reboot:
|
Reboot:
|
||||||
# init 6
|
# init 6
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,5 @@ mailfrom: info@nbit.ch
|
||||||
mailto: joerg.lehmann@nbit.ch
|
mailto: joerg.lehmann@nbit.ch
|
||||||
mailuser: nbitinf@nbit.ch
|
mailuser: nbitinf@nbit.ch
|
||||||
mailpwd: ukihefak27
|
mailpwd: ukihefak27
|
||||||
balance_ussd: "*121#"
|
balance_ussd: "*130#"
|
||||||
master_sms_number: "+41765006123"
|
|
||||||
manipulation_duration_minutes: 60
|
manipulation_duration_minutes: 60
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ class Scale(object):
|
||||||
prefix = 'accu'
|
prefix = 'accu'
|
||||||
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
|
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
|
||||||
with open(datafilename, 'a') as file:
|
with open(datafilename, 'a') as file:
|
||||||
file.write('%s,%.1f\n' % (timestamp,self.accu / 100.0))
|
file.write('%s,%.2f\n' % (timestamp,self.accu / 100.0))
|
||||||
|
|
||||||
if self.hum != INVALID_VALUE and not(swarm_alarm):
|
if self.hum != INVALID_VALUE and not(swarm_alarm):
|
||||||
prefix = 'humidity'
|
prefix = 'humidity'
|
||||||
|
|
@ -97,7 +97,7 @@ class Scale(object):
|
||||||
prefix = 'temp'
|
prefix = 'temp'
|
||||||
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
|
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
|
||||||
with open(datafilename, 'a') as file:
|
with open(datafilename, 'a') as file:
|
||||||
file.write('%s,%d\n' % (timestamp,self.temp / 10))
|
file.write('%s,%.1f\n' % (timestamp,self.temp / 10.0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ ssid=beielipi
|
||||||
# First up, the SSID or Network name. This is what other devices will see when they try to connect.
|
# First up, the SSID or Network name. This is what other devices will see when they try to connect.
|
||||||
hw_mode=g
|
hw_mode=g
|
||||||
# I'm setting this to Wireless G mode. A, B, and G are available here.
|
# I'm setting this to Wireless G mode. A, B, and G are available here.
|
||||||
channel=1
|
channel=7
|
||||||
# This is setting the channel that the WiFi is on, valid channels are from 1-11, or 1-14 depending on location.
|
# This is setting the channel that the WiFi is on, valid channels are from 1-11, or 1-14 depending on location.
|
||||||
|
|
||||||
# Wifi Security Settings
|
# Wifi Security Settings
|
||||||
|
|
@ -20,6 +20,10 @@ channel=1
|
||||||
# The line above sets the wpa passphrase to "raspiwlan", this is obtained via the wpa_passphrase command.
|
# The line above sets the wpa passphrase to "raspiwlan", this is obtained via the wpa_passphrase command.
|
||||||
# However, you can also set a passphrase like the line below.
|
# However, you can also set a passphrase like the line below.
|
||||||
#wpa_passphrase=raspiwlan
|
#wpa_passphrase=raspiwlan
|
||||||
|
wpa=2
|
||||||
|
wpa_key_mgmt=WPA-PSK
|
||||||
|
wpa_pairwise=CCMP
|
||||||
|
wpa_passphrase=beielipi
|
||||||
|
|
||||||
#wpa_key_mgmt=WPA-PSK
|
#wpa_key_mgmt=WPA-PSK
|
||||||
#wpa_pairwise=CCMP
|
#wpa_pairwise=CCMP
|
||||||
|
|
@ -30,5 +34,10 @@ channel=1
|
||||||
|
|
||||||
# Other settings
|
# Other settings
|
||||||
beacon_int=100 # This sets how often the WiFi will send a beacon out.
|
beacon_int=100 # This sets how often the WiFi will send a beacon out.
|
||||||
auth_algs=3
|
auth_algs=1
|
||||||
wmm_enabled=1
|
wmm_enabled=0
|
||||||
|
|
||||||
|
# see https://raspberrypi.stackexchange.com/questions/11713/rtl8188cus-extremely-slow
|
||||||
|
#wme_enabled=1
|
||||||
|
#ieee80211n=1
|
||||||
|
#ht_capab=[HT40+][SHORT-GI-40][DSSS_CCK-40]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#imis/internet is the apn for idea connection
|
#imis/internet is the apn for idea connection
|
||||||
#connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T gprs.swisscom.ch"
|
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T gprs.swisscom.ch"
|
||||||
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T internet"
|
#connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T internet"
|
||||||
|
|
||||||
# For Raspberry Pi3 use /dev/ttyS0 as the communication port:
|
# For Raspberry Pi3 use /dev/ttyS0 as the communication port:
|
||||||
/dev/ttyUSB0
|
/dev/ttyUSB0
|
||||||
|
|
@ -26,8 +26,8 @@ persist
|
||||||
# Do not ask the remote to authenticate.
|
# Do not ask the remote to authenticate.
|
||||||
noauth
|
noauth
|
||||||
|
|
||||||
# No hardware flow control on the serial link with GSM Modem
|
# hardware flow control on the serial link with GSM Modem
|
||||||
nocrtscts
|
crtscts
|
||||||
|
|
||||||
# No modem control lines with GSM Modem
|
# No modem control lines with GSM Modem
|
||||||
local
|
local
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ Type=simple
|
||||||
User=beieli
|
User=beieli
|
||||||
Group=beieli
|
Group=beieli
|
||||||
WorkingDirectory=/home/beieli/bin
|
WorkingDirectory=/home/beieli/bin
|
||||||
|
ExecStartPre=/bin/sleep 10
|
||||||
ExecStart=/home/beieli/bin/smsmon.py
|
ExecStart=/home/beieli/bin/smsmon.py
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Simuliert das Druecken des Powerkeys, um das GSM-Modul an- oder abzuschalten
|
|
||||||
#
|
|
||||||
import time
|
|
||||||
import RPi.GPIO as GPIO
|
|
||||||
|
|
||||||
# RPi.GPIO Layout verwenden (wie Pin-Nummern)
|
|
||||||
GPIO.setmode(GPIO.BOARD)
|
|
||||||
|
|
||||||
# Pin 11 (GPIO 17) auf Output setzen
|
|
||||||
GPIO.setup(11, GPIO.OUT)
|
|
||||||
|
|
||||||
# Druecken simulieren
|
|
||||||
GPIO.output(11, GPIO.HIGH)
|
|
||||||
|
|
||||||
# Pause, mindestens eine Sekunde gemaess https://www.itead.cc/wiki/RPI_SIM800_GSM/GPRS_ADD-ON_V2.0
|
|
||||||
time.sleep(1.5)
|
|
||||||
|
|
||||||
# Weg vom Taster...
|
|
||||||
GPIO.output(11, GPIO.LOW)
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
GPIO.cleanup()
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# GSM-Modul anschalten, falls ein keine Antwort gibt...
|
|
||||||
#
|
|
||||||
import time
|
|
||||||
import RPi.GPIO as GPIO
|
|
||||||
import serial
|
|
||||||
|
|
||||||
def PowerOn():
|
|
||||||
# RPi.GPIO Layout verwenden (wie Pin-Nummern)
|
|
||||||
GPIO.setmode(GPIO.BOARD)
|
|
||||||
|
|
||||||
# Pin 11 (GPIO 17) auf Output setzen
|
|
||||||
GPIO.setup(11, GPIO.OUT)
|
|
||||||
|
|
||||||
# Druecken simulieren
|
|
||||||
GPIO.output(11, GPIO.HIGH)
|
|
||||||
|
|
||||||
# Pause, mindestens eine Sekunde gemaess https://www.itead.cc/wiki/RPI_SIM800_GSM/GPRS_ADD-ON_V2.0
|
|
||||||
time.sleep(1.5)
|
|
||||||
|
|
||||||
# Weg vom Taster...
|
|
||||||
GPIO.output(11, GPIO.LOW)
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
GPIO.cleanup()
|
|
||||||
|
|
||||||
with serial.Serial('/dev/ttyAMA0', 115200, timeout=2) as ser:
|
|
||||||
ser.write(b'AT\n')
|
|
||||||
line = ser.readline()
|
|
||||||
line = ser.readline()
|
|
||||||
print "Feedback: %s" % (line)
|
|
||||||
if line.strip() != "OK":
|
|
||||||
print "Modem reagiert nicht, evtl. abgeschaltet... wir schalten es ein..."
|
|
||||||
PowerOn()
|
|
||||||
ser.close() # close port
|
|
||||||
|
|
@ -128,6 +128,7 @@ def GetInfoText():
|
||||||
['uname', '-a'],
|
['uname', '-a'],
|
||||||
['df','-h','/boot','/root'],
|
['df','-h','/boot','/root'],
|
||||||
['free','-m'],
|
['free','-m'],
|
||||||
|
['/home/beieli/root-bin/show-bt-readings'],
|
||||||
['ip','a'] ]:
|
['ip','a'] ]:
|
||||||
commands_output = "%s\n# %s\n%s" % (commands_output,' '.join(comm_arr),subprocess.check_output(comm_arr))
|
commands_output = "%s\n# %s\n%s" % (commands_output,' '.join(comm_arr),subprocess.check_output(comm_arr))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
find /home/beieli/bt-readings -type f -print -exec cat {} \;
|
||||||
|
echo
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3>BeieliPi Zugang via WLAN-Hotspot</h3>
|
<h3>BeieliPi Zugang via WLAN-Hotspot</h3>
|
||||||
<p>Zugang zu dieser Seite: Verbinden (WLAN) mit SSID <strong>beielipi</strong> (kein Passwort), dann Aufruf von <strong>http://beielipi.local</strong></p>
|
<p>Zugang zu dieser Seite: Verbinden (WLAN) mit SSID <strong>beielipi</strong> (Passwort: beielipi), dann Aufruf von <strong>http://beielipi.local</strong></p>
|
||||||
<p>Hotspot aktivieren per SMS an {{ beielipi_mobile_number }}, Text: "hotspot on" (zum Deaktivieren: "hotspot off")</p>
|
<p>Hotspot aktivieren per SMS an {{ beielipi_mobile_number }}, Text: "hotspot on" (zum Deaktivieren: "hotspot off")</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Dygraph;
|
||||||
|
if (window.Dygraph) {
|
||||||
|
Dygraph = window.Dygraph;
|
||||||
|
} else if (typeof(module) !== 'undefined') {
|
||||||
|
Dygraph = require('../dygraph');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given three sequential points, p0, p1 and p2, find the left and right
|
||||||
|
* control points for p1.
|
||||||
|
*
|
||||||
|
* The three points are expected to have x and y properties.
|
||||||
|
*
|
||||||
|
* The alpha parameter controls the amount of smoothing.
|
||||||
|
* If α=0, then both control points will be the same as p1 (i.e. no smoothing).
|
||||||
|
*
|
||||||
|
* Returns [l1x, l1y, r1x, r1y]
|
||||||
|
*
|
||||||
|
* It's guaranteed that the line from (l1x, l1y)-(r1x, r1y) passes through p1.
|
||||||
|
* Unless allowFalseExtrema is set, then it's also guaranteed that:
|
||||||
|
* l1y ∈ [p0.y, p1.y]
|
||||||
|
* r1y ∈ [p1.y, p2.y]
|
||||||
|
*
|
||||||
|
* The basic algorithm is:
|
||||||
|
* 1. Put the control points l1 and r1 α of the way down (p0, p1) and (p1, p2).
|
||||||
|
* 2. Shift l1 and r2 so that the line l1–r1 passes through p1
|
||||||
|
* 3. Adjust to prevent false extrema while keeping p1 on the l1–r1 line.
|
||||||
|
*
|
||||||
|
* This is loosely based on the HighCharts algorithm.
|
||||||
|
*/
|
||||||
|
function getControlPoints(p0, p1, p2, opt_alpha, opt_allowFalseExtrema) {
|
||||||
|
var alpha = (opt_alpha !== undefined) ? opt_alpha : 1/3; // 0=no smoothing, 1=crazy smoothing
|
||||||
|
var allowFalseExtrema = opt_allowFalseExtrema || false;
|
||||||
|
|
||||||
|
if (!p2) {
|
||||||
|
return [p1.x, p1.y, null, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Position the control points along each line segment.
|
||||||
|
var l1x = (1 - alpha) * p1.x + alpha * p0.x,
|
||||||
|
l1y = (1 - alpha) * p1.y + alpha * p0.y,
|
||||||
|
r1x = (1 - alpha) * p1.x + alpha * p2.x,
|
||||||
|
r1y = (1 - alpha) * p1.y + alpha * p2.y;
|
||||||
|
|
||||||
|
// Step 2: shift the points up so that p1 is on the l1–r1 line.
|
||||||
|
if (l1x != r1x) {
|
||||||
|
// This can be derived w/ some basic algebra.
|
||||||
|
var deltaY = p1.y - r1y - (p1.x - r1x) * (l1y - r1y) / (l1x - r1x);
|
||||||
|
l1y += deltaY;
|
||||||
|
r1y += deltaY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: correct to avoid false extrema.
|
||||||
|
if (!allowFalseExtrema) {
|
||||||
|
if (l1y > p0.y && l1y > p1.y) {
|
||||||
|
l1y = Math.max(p0.y, p1.y);
|
||||||
|
r1y = 2 * p1.y - l1y;
|
||||||
|
} else if (l1y < p0.y && l1y < p1.y) {
|
||||||
|
l1y = Math.min(p0.y, p1.y);
|
||||||
|
r1y = 2 * p1.y - l1y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r1y > p1.y && r1y > p2.y) {
|
||||||
|
r1y = Math.max(p1.y, p2.y);
|
||||||
|
l1y = 2 * p1.y - r1y;
|
||||||
|
} else if (r1y < p1.y && r1y < p2.y) {
|
||||||
|
r1y = Math.min(p1.y, p2.y);
|
||||||
|
l1y = 2 * p1.y - r1y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [l1x, l1y, r1x, r1y];
|
||||||
|
}
|
||||||
|
|
||||||
|
// i.e. is none of (null, undefined, NaN)
|
||||||
|
function isOK(x) {
|
||||||
|
return !!x && !isNaN(x);
|
||||||
|
};
|
||||||
|
|
||||||
|
// A plotter which uses splines to create a smooth curve.
|
||||||
|
// See tests/plotters.html for a demo.
|
||||||
|
// Can be controlled via smoothPlotter.smoothing
|
||||||
|
function smoothPlotter(e) {
|
||||||
|
var ctx = e.drawingContext,
|
||||||
|
points = e.points;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(points[0].canvasx, points[0].canvasy);
|
||||||
|
|
||||||
|
// right control point for previous point
|
||||||
|
var lastRightX = points[0].canvasx, lastRightY = points[0].canvasy;
|
||||||
|
|
||||||
|
for (var i = 1; i < points.length; i++) {
|
||||||
|
var p0 = points[i - 1],
|
||||||
|
p1 = points[i],
|
||||||
|
p2 = points[i + 1];
|
||||||
|
p0 = p0 && isOK(p0.canvasy) ? p0 : null;
|
||||||
|
p1 = p1 && isOK(p1.canvasy) ? p1 : null;
|
||||||
|
p2 = p2 && isOK(p2.canvasy) ? p2 : null;
|
||||||
|
if (p0 && p1) {
|
||||||
|
var controls = getControlPoints({x: p0.canvasx, y: p0.canvasy},
|
||||||
|
{x: p1.canvasx, y: p1.canvasy},
|
||||||
|
p2 && {x: p2.canvasx, y: p2.canvasy},
|
||||||
|
smoothPlotter.smoothing);
|
||||||
|
// Uncomment to show the control points:
|
||||||
|
// ctx.lineTo(lastRightX, lastRightY);
|
||||||
|
// ctx.lineTo(controls[0], controls[1]);
|
||||||
|
// ctx.lineTo(p1.canvasx, p1.canvasy);
|
||||||
|
lastRightX = (lastRightX !== null) ? lastRightX : p0.canvasx;
|
||||||
|
lastRightY = (lastRightY !== null) ? lastRightY : p0.canvasy;
|
||||||
|
ctx.bezierCurveTo(lastRightX, lastRightY,
|
||||||
|
controls[0], controls[1],
|
||||||
|
p1.canvasx, p1.canvasy);
|
||||||
|
lastRightX = controls[2];
|
||||||
|
lastRightY = controls[3];
|
||||||
|
} else if (p1) {
|
||||||
|
// We're starting again after a missing point.
|
||||||
|
ctx.moveTo(p1.canvasx, p1.canvasy);
|
||||||
|
lastRightX = p1.canvasx;
|
||||||
|
lastRightY = p1.canvasy;
|
||||||
|
} else {
|
||||||
|
lastRightX = lastRightY = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
smoothPlotter.smoothing = 1/3;
|
||||||
|
smoothPlotter._getControlPoints = getControlPoints; // for testing
|
||||||
|
|
||||||
|
// older versions exported a global.
|
||||||
|
// This will be removed in the future.
|
||||||
|
// The preferred way to access smoothPlotter is via Dygraph.smoothPlotter.
|
||||||
|
window.smoothPlotter = smoothPlotter;
|
||||||
|
Dygraph.smoothPlotter = smoothPlotter;
|
||||||
|
|
||||||
|
})();
|
||||||
Loading…
Reference in New Issue